"""استيراد تقرير أوردرات كذا ميزا (Excel أو CSV) ودمجه في orders.csv.

التقرير اللي بتنزّله من لوحة كذا ميزا ("ملخص طلباتي") ليه نفس أعمدة
orders.csv بالظبط، فالاستيراد هنا بيركز على التحقق والدمج الآمن:

  * بيقرأ xlsx بدون أي مكتبات خارجية (zipfile + XML).
  * بيقبل CSV كذلك.
  * بيدمج حسب "رقم الطلب": الجديد بيكسب، والأوردرات القديمة اللي مش
    في الملف الجديد بتفضل موجودة.
  * بياخد نسخة احتياطية من orders.csv قبل أي كتابة.
"""

import base64
import csv
import io
import json
import os
import re
import shutil
import zipfile
from datetime import datetime
from xml.etree import ElementTree as ET

DATA_DIR = os.path.dirname(os.path.abspath(__file__))
ORDERS_CSV = os.path.join(DATA_DIR, "orders.csv")
ORDERS_FILES = {
    'kzamiza': ORDERS_CSV,
    'vendor': os.path.join(DATA_DIR, "orders_vendor.csv"),
    'easy': os.path.join(DATA_DIR, "orders_easy.csv"),
}
LINE_NAMES = {'kzamiza': 'كذا ميزا', 'vendor': 'فيندور', 'easy': 'إيزي أوردر'}
BACKUP_DIR = os.path.join(DATA_DIR, "backups")
STATE_FILE = os.path.join(DATA_DIR, "orders_state.json")

COLUMNS = [
    'تاريخ الإنشاء', 'رقم الطلب', 'اسم العميل', 'رقم العميل', 'الرقم البديل',
    'المحافظة', 'المدينة', 'العنوان', 'حالة الطلب', 'حالة الشحن',
    'تاريخ آخر تحديث', 'الإجمالي', 'عدد القطع', 'المحتوى', 'العمولة', 'نزلت المحفظة',
]

REQUIRED = ['تاريخ الإنشاء', 'رقم الطلب', 'حالة الطلب', 'حالة الشحن',
            'الإجمالي', 'المحتوى', 'العمولة']

ALIASES = {
    'تاريخ الانشاء': 'تاريخ الإنشاء', 'date': 'تاريخ الإنشاء', 'created at': 'تاريخ الإنشاء',
    'order id': 'رقم الطلب', 'id': 'رقم الطلب', 'الطلب': 'رقم الطلب',
    'customer': 'اسم العميل', 'customer name': 'اسم العميل', 'العميل': 'اسم العميل',
    'phone': 'رقم العميل', 'الموبايل': 'رقم العميل',
    'government': 'المحافظة', 'المحافظة': 'المحافظة',
    'city': 'المدينة', 'status': 'حالة الطلب',
    'shipping status': 'حالة الشحن', 'updated at': 'تاريخ آخر تحديث',
    'total': 'الإجمالي', 'الإجمالي': 'الإجمالي', 'quantity': 'عدد القطع',
    'items': 'المحتوى', 'products': 'المحتوى', 'commission': 'العمولة',
    'wallet': 'نزلت المحفظة',
}

_NS = '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}'


class OrdersImportError(Exception):
    pass


# --------------------------------------------------------------------------
# قراءة الملفات
# --------------------------------------------------------------------------

def _clean(value):
    if value is None:
        return ''
    if isinstance(value, float):
        return str(int(value)) if value.is_integer() else str(value)
    s = str(value).replace('\ufeff', '').strip()
    return s


def _column_index(ref):
    letters = re.match(r'([A-Z]+)', ref or '')
    if not letters:
        return None
    idx = 0
    for ch in letters.group(1):
        idx = idx * 26 + (ord(ch) - 64)
    return idx - 1


def _xlsx_sheet_path(zf):
    """أول شيت في الملف."""
    try:
        wb = ET.fromstring(zf.read('xl/workbook.xml'))
        first = wb.find(f'{_NS}sheets/{_NS}sheet')
        rel_id = first.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id')
        rels = ET.fromstring(zf.read('xl/_rels/workbook.xml.rels'))
        for rel in rels:
            if rel.get('Id') == rel_id:
                target = rel.get('Target').lstrip('/')
                return target if target.startswith('xl/') else 'xl/' + target
    except Exception:
        pass
    sheets = sorted(n for n in zf.namelist() if re.match(r'xl/worksheets/sheet\d+\.xml$', n))
    if not sheets:
        raise OrdersImportError("الملف مش ملف Excel صالح")
    return sheets[0]


def _read_xlsx(blob):
    try:
        zf = zipfile.ZipFile(io.BytesIO(blob))
    except zipfile.BadZipFile:
        raise OrdersImportError("الملف تالف أو مش Excel")

    shared = []
    if 'xl/sharedStrings.xml' in zf.namelist():
        root = ET.fromstring(zf.read('xl/sharedStrings.xml'))
        for si in root.findall(f'{_NS}si'):
            shared.append(''.join(t.text or '' for t in si.iter(f'{_NS}t')))

    root = ET.fromstring(zf.read(_xlsx_sheet_path(zf)))
    rows = []
    for row in root.iter(f'{_NS}row'):
        values = {}
        for cell in row.findall(f'{_NS}c'):
            idx = _column_index(cell.get('r'))
            if idx is None:
                continue
            ctype = cell.get('t')
            if ctype == 'inlineStr':
                node = cell.find(f'{_NS}is')
                text = ''.join(t.text or '' for t in node.iter(f'{_NS}t')) if node is not None else ''
            else:
                v = cell.find(f'{_NS}v')
                text = v.text if v is not None and v.text is not None else ''
                if ctype == 's' and text != '':
                    try:
                        text = shared[int(text)]
                    except (ValueError, IndexError):
                        text = ''
            values[idx] = text
        if values:
            width = max(values) + 1
            rows.append([_clean(values.get(i, '')) for i in range(width)])
        else:
            rows.append([])
    return rows


def _read_csv(blob):
    for enc in ('utf-8-sig', 'utf-8', 'cp1256', 'latin-1'):
        try:
            text = blob.decode(enc)
            break
        except UnicodeDecodeError:
            continue
    else:
        text = blob.decode('utf-8', 'replace')

    sample = text[:2000]
    delimiter = '\t' if sample.count('\t') > sample.count(',') else ','
    if sample.count(';') > sample.count(','):
        delimiter = ';'
    return [row for row in csv.reader(io.StringIO(text), delimiter=delimiter)]


def _normalize_header(value):
    s = _clean(value).lower()
    s = re.sub(r'\s+', ' ', s).strip()
    return s


def _map_headers(header_row):
    mapping = {}
    for i, raw in enumerate(header_row):
        name = _clean(raw)
        if not name:
            continue
        if name in COLUMNS:
            mapping[name] = i
            continue
        alias = ALIASES.get(_normalize_header(name))
        if alias and alias not in mapping:
            mapping[alias] = i
    missing = [c for c in REQUIRED if c not in mapping]
    if missing:
        raise OrdersImportError(
            "الملف ناقص أعمدة: " + "، ".join(missing) +
            ". لازم يكون تقرير أوردرات كذا ميزا (ملخص طلباتي).")
    return mapping


def _fix_date(value):
    s = _clean(value)
    if not s:
        return ''
    if re.fullmatch(r'\d+(\.\d+)?', s):
        try:
            serial = float(s)
            if serial > 20000:
                from datetime import timedelta
                d = datetime(1899, 12, 30) + timedelta(days=serial)
                return d.strftime('%d-%m-%Y')
        except ValueError:
            pass
    return s


VENDOR_STATUS_MAP = {
    'commission paid': 'تم تسليمها للعميل',
    'collected': 'تم تسليمها للعميل',
    'delivered': 'تم تسليمها للعميل',
    'returned from shipping and recive': 'مرتجع في المخزن',
    'returned from shipping but not recive': 'مرتجع في المخزن',
    'canceled': 'ملغي',
    'refunded': 'ملغي',
    'refunded commission': 'ملغي',
    'shipped': 'خرج للشحن',
    'printed': 'جاهز للشحن',
    'processing': 'جاهز للشحن',
    'new': 'غير مؤكد',
    'pending': 'غير مؤكد',
    'partial delivery': 'غير مؤكد',
}

VENDOR_WALLET_STATUSES = ('commission paid', 'collected')


def _vendor_number(value):
    s = _clean(value).replace(',', '').replace('\u066c', '').replace('\u066b', '.')
    try:
        return float(s)
    except ValueError:
        return 0.0


def _vendor_datetime(value):
    s = _clean(value)
    if not s:
        return None
    for fmt in ('%Y-%m-%d %I:%M %p', '%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M',
                '%Y-%m-%d', '%d-%m-%Y %H:%M:%S', '%d-%m-%Y'):
        try:
            return datetime.strptime(s, fmt)
        except ValueError:
            continue
    try:
        return datetime.fromisoformat(s)
    except ValueError:
        return None


def parse_vendor_file(blob):
    """تقرير فيندور: أعمدة إنجليزية/عربية مختلفة وحالات إنجليزية."""
    table = _read_csv(blob)
    table = [r for r in table if any(_clean(c) for c in r)]
    if not table:
        raise OrdersImportError("الملف فاضي")

    header = [_clean(h) for h in table[0]]
    idx = {}
    for i, h in enumerate(header):
        if h and h not in idx:
            idx[h] = i

    required = ['كود الاوردر', 'حالة الاوردر', 'تاريخ انشاء الاوردر',
                'العمولة', 'الأصناف', 'الإجمالي']
    missing = [c for c in required if c not in idx]
    if missing:
        raise OrdersImportError(
            "تقرير فيندور ناقص أعمدة: " + "، ".join(missing) +
            ". صدّره من لوحة فيندور بنفس الأعمدة الأصلية.")

    def cell(row, col):
        i = idx.get(col)
        return _clean(row[i]) if i is not None and i < len(row) else ''

    records = []
    for row in table[1:]:
        code = cell(row, 'كود الاوردر')
        if not code:
            continue
        created = _vendor_datetime(cell(row, 'تاريخ انشاء الاوردر'))
        if not created:
            continue
        raw_status = cell(row, 'حالة الاوردر').strip().lower()
        status = VENDOR_STATUS_MAP.get(raw_status, 'غير مؤكد')
        wallet = 'نعم' if raw_status in VENDOR_WALLET_STATUSES else 'لا'
        updated = _vendor_datetime(cell(row, 'تاريخ و وقت اخر ابديت'))

        records.append({
            'تاريخ الإنشاء': created.strftime('%d-%m-%Y'),
            'رقم الطلب': code,
            'اسم العميل': cell(row, 'اسم العميل'),
            'رقم العميل': cell(row, 'رقم موبايل العميل'),
            'الرقم البديل': '',
            'المحافظة': cell(row, 'المحافظة'),
            'المدينة': cell(row, 'المدينة'),
            'العنوان': cell(row, 'المدينة'),
            'حالة الطلب': status,
            'حالة الشحن': status,
            'تاريخ آخر تحديث': updated.strftime('%d-%m-%Y') if updated else '',
            'الإجمالي': cell(row, 'الإجمالي'),
            'عدد القطع': cell(row, 'عدد القطع'),
            'المحتوى': cell(row, 'الأصناف'),
            'العمولة': cell(row, 'العمولة'),
            'نزلت المحفظة': wallet,
        })

    if not records:
        raise OrdersImportError("مفيش أوردرات صالحة في تقرير فيندور")
    return records


def parse_file(filename, content, source='kzamiza'):
    """يرجّع صفوف dict بالأعمدة القياسية."""
    blob = base64.b64decode(content) if isinstance(content, str) else content
    if source == 'vendor':
        return parse_vendor_file(blob)
    name = (filename or '').lower()

    if name.endswith('.xlsx') or blob[:2] == b'PK':
        table = _read_xlsx(blob)
    else:
        table = _read_csv(blob)

    table = [r for r in table if any(_clean(c) for c in r)]
    if not table:
        raise OrdersImportError("الملف فاضي")

    header_idx, mapping = None, None
    for i, row in enumerate(table[:10]):
        try:
            mapping = _map_headers(row)
            header_idx = i
            break
        except OrdersImportError:
            continue
    if mapping is None:
        raise OrdersImportError(
            "مش لاقي أعمدة التقرير. تأكد إنك بتصدّر تقرير الأوردرات من لوحة كذا ميزا.")

    records = []
    for row in table[header_idx + 1:]:
        if not any(_clean(c) for c in row):
            continue
        rec = {}
        for col, pos in mapping.items():
            rec[col] = _clean(row[pos]) if pos < len(row) else ''
        if not rec.get('رقم الطلب'):
            continue
        rec['تاريخ الإنشاء'] = _fix_date(rec.get('تاريخ الإنشاء'))
        for col in COLUMNS:
            rec.setdefault(col, '')
        if not re.fullmatch(r'\d{2}-\d{2}-\d{4}', rec['تاريخ الإنشاء'] or ''):
            continue
        records.append(rec)
    if not records:
        raise OrdersImportError("مفيش صفوف أوردرات صالحة في الملف")
    return records


# --------------------------------------------------------------------------
# الحالة والدمج
# --------------------------------------------------------------------------

def _read_state():
    if os.path.exists(STATE_FILE):
        try:
            with open(STATE_FILE, 'r', encoding='utf-8') as f:
                data = json.load(f)
            # ترحيل الصيغة القديمة (قبل تعدد الخطوط)
            if 'last_import' in data:
                data.setdefault('kzamiza', {'last_import': data['last_import']})
            return data
        except Exception:
            pass
    return {}


def _write_state(state):
    with open(STATE_FILE, 'w', encoding='utf-8') as f:
        json.dump(state, f, ensure_ascii=False, indent=2)


def load_orders(source='kzamiza'):
    path = ORDERS_FILES.get(source, ORDERS_CSV)
    if not os.path.exists(path):
        return []
    with open(path, 'r', encoding='utf-8-sig', newline='') as f:
        return list(csv.DictReader(f))


def _date_key(value):
    try:
        return datetime.strptime(value, '%d-%m-%Y')
    except (TypeError, ValueError):
        return datetime.min


def summary(source='kzamiza'):
    rows = load_orders(source)
    dates = [r.get('تاريخ الإنشاء', '') for r in rows]
    valid = [d for d in dates if re.fullmatch(r'\d{2}-\d{2}-\d{4}', d or '')]
    state = _read_state().get(source) or {}
    return {
        'source': source,
        'rows': len(rows),
        'first_date': min(valid, key=_date_key) if valid else '',
        'last_date': max(valid, key=_date_key) if valid else '',
        'last_import': state.get('last_import'),
    }


def import_orders(filename, content, source='kzamiza'):
    target = ORDERS_FILES.get(source)
    if not target:
        raise OrdersImportError("مصدر أوردرات غير معروف")
    records = parse_file(filename, content, source)
    existing = load_orders(source)

    incoming_ids = {r['رقم الطلب'] for r in records}
    existing_ids = {r.get('رقم الطلب', '') for r in existing}
    added = len(incoming_ids - existing_ids)
    updated = len(incoming_ids & existing_ids)

    merged = {r['رقم الطلب']: r for r in records}
    for row in existing:
        oid = row.get('رقم الطلب', '')
        if oid and oid not in merged:
            merged[oid] = {col: row.get(col, '') for col in COLUMNS}

    rows = list(merged.values())
    rows.sort(key=lambda r: (_date_key(r.get('تاريخ الإنشاء')), r.get('رقم الطلب', '')), reverse=True)

    os.makedirs(BACKUP_DIR, exist_ok=True)
    stamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    if os.path.exists(target):
        shutil.copy2(target, os.path.join(BACKUP_DIR, f"{source}_backup_{stamp}.csv"))

    tmp = target + '.tmp'
    with open(tmp, 'w', encoding='utf-8-sig', newline='') as f:
        writer = csv.DictWriter(f, fieldnames=COLUMNS, extrasaction='ignore')
        writer.writeheader()
        writer.writerows(rows)
    os.replace(tmp, target)

    dates = [r.get('تاريخ الإنشاء', '') for r in rows if r.get('تاريخ الإنشاء')]
    commission = 0.0
    for r in rows:
        try:
            commission += float((r.get('العمولة') or '0').replace(',', ''))
        except ValueError:
            pass

    state = _read_state()
    state[source] = {
        'last_import': {
            'at': datetime.now().strftime('%Y-%m-%d %H:%M'),
            'file': filename,
            'rows': len(rows),
            'added': added,
            'updated': updated,
        }
    }
    _write_state(state)

    return {
        'rows': len(rows),
        'added': added,
        'updated': updated,
        'first_date': min(dates, key=_date_key) if dates else '',
        'last_date': max(dates, key=_date_key) if dates else '',
        'commission_total': round(commission, 2),
        'backup': f"backups/{source}_backup_{stamp}.csv",
    }
