"""
src/core/drive_sync.py — Google Drive Backup via OAuth 2.0
---------------------------------------------------------
Cấu trúc thư mục:
  📁 {MST}/            ← tên thư mục gốc là mã số thuế
    📁 doanhthu/      ← backup + sync thu chi
    📁 contracts/     ← hợp đồng (sale / purchase)
"""
import os
import glob
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload
from imap_tools import MailBox, AND
import shutil
import json
from datetime import datetime
import re

from core.utils import get_app_data_path, get_company_root_path, get_resource_path

# Chỉ xin quyền TẠO và CHỈNH SỬA các file do chính ứng dụng này tải lên.
SCOPES = ['https://www.googleapis.com/auth/drive.file']

# Tên thư mục gốc cố định trên Google Drive
def get_drive_parent_name():
    try:
        settings_path = _get_settings_path()
        if os.path.exists(settings_path):
            with open(settings_path, "r", encoding="utf-8") as f:
                info = json.load(f).get("company", {})
                mst = info.get("tax_id", "").strip()
                if mst: return mst
    except: pass
    return "INIHD"

DRIVE_FOLDER_NAME = "doanhthu"

_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
_SRC_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


def _get_settings_path() -> str:
    return get_app_data_path("settings.json")


def _get_token_file() -> str:
    return get_app_data_path(os.path.join("google_auth", "drive_token.json"))


def _find_client_secret_file() -> str:
    candidate_dirs: list[str] = []
    for path in (
        get_app_data_path(""),
        _SRC_ROOT,
        get_resource_path(""),
        os.path.join(get_resource_path(""), "src"),
    ):
        normalized = os.path.abspath(path)
        if os.path.isdir(normalized) and normalized not in candidate_dirs:
            candidate_dirs.append(normalized)

    for directory in candidate_dirs:
        matches = sorted(glob.glob(os.path.join(directory, "client_secret*.json")))
        if matches:
            return matches[0]

    for directory in candidate_dirs:
        fallback = os.path.join(directory, "client_secret.json")
        if os.path.exists(fallback):
            return fallback

    return ""


def get_drive_service_via_oauth():
    """Bật trình duyệt xin quyền (nếu chưa có), sau đó trả về Drive API Service."""
    creds = None
    token_file = _get_token_file()
    client_secret_file = _find_client_secret_file()

    # Đọc token nếu đã đăng nhập từ trước
    if os.path.exists(token_file):
        try:
            creds = Credentials.from_authorized_user_file(token_file, SCOPES)
        except Exception as e:
            print(f"Lỗi đọc token cũ: {e}")
            creds = None

    # Nếu chưa đăng nhập hoặc token hết hạn
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            print("Token hết hạn, đang lấy token mới...")
            try:
                creds.refresh(Request())
            except Exception as e:
                print(f"Lỗi gia hạn quyền: {e}. Sẽ tiến hành đăng nhập lại.")
                creds = None

        if not creds:
            if not client_secret_file or not os.path.exists(client_secret_file):
                raise FileNotFoundError(
                    "Không tìm thấy file Client Secret của Google Drive.\n"
                    "Vui lòng đặt file client_secret*.json cạnh file chạy hoặc build lại bản EXE có kèm credentials."
                )

            flow = InstalledAppFlow.from_client_secrets_file(client_secret_file, SCOPES)
            print("Vui lòng xác nhận trên trình duyệt đang mở...")
            creds = flow.run_local_server(port=0)

        # Lưu token lại để dùng cho lần sau
        os.makedirs(os.path.dirname(token_file), exist_ok=True)
        with open(token_file, 'w', encoding='utf-8') as token:
            token.write(creds.to_json())

    return build('drive', 'v3', credentials=creds)


def _get_or_create_folder(service, name: str, parent_id: str = None) -> str:
    """Tìm hoặc tạo một thư mục trên Drive."""
    query = (
        f"name = '{name}' "
        f"and mimeType = 'application/vnd.google-apps.folder' "
        f"and trashed = false"
    )
    if parent_id:
        query += f" and '{parent_id}' in parents"
    
    results = service.files().list(
        q=query,
        spaces='drive',
        fields='files(id, name)',
        pageSize=1
    ).execute()
    
    folders = results.get('files', [])
    if folders:
        return folders[0]['id']
    
    # Tạo mới
    folder_metadata = {
        'name': name,
        'mimeType': 'application/vnd.google-apps.folder'
    }
    if parent_id:
        folder_metadata['parents'] = [parent_id]
        
    folder = service.files().create(
        body=folder_metadata,
        fields='id'
    ).execute()
    return folder.get('id')


def _get_or_create_hkd_folder(service) -> str:
    """
    Tìm thư mục 'INIHD/{MST}/doanhthu' trên Drive. Nếu chưa có thì tạo mới.
    """
    # 1. Parent INIHD/{MST}
    parent_id = _get_or_create_path(service, ["INIHD", get_drive_parent_name()])
    # 2. Child KD
    child_id = _get_or_create_folder(service, DRIVE_FOLDER_NAME, parent_id)
    
    print(f"✅ Thư mục Drive: INIHD/{get_drive_parent_name()}/{DRIVE_FOLDER_NAME} (id: {child_id})")
    return child_id


def _get_or_create_path(service, path_list: list) -> str:
    """Tạo hoặc tìm một cây thư mục phân cấp trên Drive."""
    parent_id = None
    last_id = None
    for folder_name in path_list:
        last_id = _get_or_create_folder(service, folder_name, parent_id)
        parent_id = last_id
    return last_id


def backup_file_to_drive(local_file_path: str) -> dict:
    """
    Upload một file (.zip) lên thư mục HKD trên Google Drive.
    Tự động tạo thư mục HKD nếu chưa tồn tại.
    """
    if not os.path.exists(local_file_path):
        return {"success": False, "message": f"File backup '{local_file_path}' không tồn tại!"}

    try:
        service = get_drive_service_via_oauth()
    except Exception as e:
        return {"success": False, "message": f"Lỗi xác thực Google Drive: {e}"}

    # Lấy hoặc tạo thư mục HKD
    try:
        folder_id = _get_or_create_hkd_folder(service)
    except Exception as e:
        return {"success": False, "message": f"Lỗi tạo thư mục HKD trên Drive: {e}"}

    file_name = os.path.basename(local_file_path)
    file_metadata = {
        'name': file_name,
        'parents': [folder_id]  # Upload VÀO thư mục HKD
    }

    mime_type = 'application/zip' if local_file_path.endswith('.zip') else 'application/octet-stream'
    media = MediaFileUpload(local_file_path, mimetype=mime_type, resumable=True)

    try:
        print(f"⬆️  Đang upload '{file_name}' → Drive/INIHD/{get_drive_parent_name()}/doanhthu/ ...")
        uploaded_file = service.files().create(
            body=file_metadata,
            media_body=media,
            fields='id, webViewLink, name'
        ).execute()

        link = uploaded_file.get('webViewLink', '')
        name = uploaded_file.get('name', file_name)
        print(f"✅ Upload hoàn thành: {name} → {link}")
        return {
            "success": True,
            "message": f"Đã upload '{name}' vào thư mục HKD trên Google Drive!",
            "link": link,
            "folder": DRIVE_FOLDER_NAME
        }
    except Exception as e:
        return {"success": False, "message": f"Lỗi khi upload lên Drive: {e}"}


def list_drive_hkd_files() -> list:
    """
    Liệt kê các file trong thư mục HKD trên Google Drive.
    Dùng để kiểm tra xem đã backup những file nào rồi.
    """
    try:
        service = get_drive_service_via_oauth()
        folder_id = _get_or_create_hkd_folder(service)

        results = service.files().list(
            q=f"'{folder_id}' in parents and trashed = false",
            spaces='drive',
            fields='files(id, name, size, modifiedTime, webViewLink)',
            orderBy='modifiedTime desc',
            pageSize=20
        ).execute()

        files = results.get('files', [])
        print(f"\n📂 Nội dung thư mục HKD trên Drive ({len(files)} file):")
        for f in files:
            size_kb = int(f.get('size', 0)) / 1024
            print(f"  - {f['name']}  ({size_kb:.1f} KB)  {f.get('modifiedTime', '')[:10]}")
        return files
    except Exception as e:
        print(f"❌ Lỗi liệt kê file Drive: {e}")
        return []


def list_drive_backup_files(page_size: int = 30) -> dict:
    """
    Liệt kê các file backup .zip trong thư mục HKD trên Google Drive.
    Trả về dict để UI có thể phân biệt rõ lỗi xác thực với trường hợp không có file.
    """
    try:
        service = get_drive_service_via_oauth()
        folder_id = _get_or_create_hkd_folder(service)
        results = service.files().list(
            q=f"'{folder_id}' in parents and trashed = false",
            spaces='drive',
            fields='files(id, name, size, modifiedTime, webViewLink)',
            orderBy='modifiedTime desc',
            pageSize=page_size,
        ).execute()
        files = results.get('files', [])
        backup_files = [item for item in files if str(item.get('name', '')).lower().endswith('.zip')]
        return {
            "success": True,
            "files": backup_files,
            "message": f"Tìm thấy {len(backup_files)} file backup trên Google Drive.",
        }
    except Exception as e:
        return {
            "success": False,
            "files": [],
            "message": f"Không lấy được danh sách backup trên Google Drive: {e}",
        }


def download_backup_from_drive(file_id: str, file_name: str = "") -> dict:
    """
    Tải file backup từ Google Drive về thư mục backup cục bộ để restore.
    """
    file_id = str(file_id or "").strip()
    if not file_id:
        return {"success": False, "message": "Thiếu file_id để tải backup từ Google Drive."}

    safe_name = str(file_name or "").strip() or f"DriveBackup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
    safe_name = re.sub(r'[\\/*?:"<>|]+', '_', safe_name)
    if not safe_name.lower().endswith('.zip'):
        safe_name += '.zip'

    download_dir = get_company_root_path(os.path.join("database", "backups", "drive_downloads"))
    os.makedirs(download_dir, exist_ok=True)

    dest_path = os.path.join(download_dir, safe_name)
    if os.path.exists(dest_path):
        stem, ext = os.path.splitext(safe_name)
        stamped_name = f"{stem}_{datetime.now().strftime('%Y%m%d_%H%M%S')}{ext}"
        dest_path = os.path.join(download_dir, stamped_name)

    try:
        service = get_drive_service_via_oauth()
        request = service.files().get_media(fileId=file_id)

        with open(dest_path, 'wb') as file_obj:
            downloader = MediaIoBaseDownload(file_obj, request)
            done = False
            while not done:
                _, done = downloader.next_chunk()

        return {
            "success": True,
            "message": f"Đã tải backup từ Google Drive về {dest_path}",
            "path": dest_path,
            "name": os.path.basename(dest_path),
        }
    except Exception as e:
        try:
            if os.path.exists(dest_path):
                os.remove(dest_path)
        except Exception:
            pass
        return {"success": False, "message": f"Không tải được backup từ Google Drive: {e}"}


# ===================== CONTRACT SYNC =====================

def _get_email_config():
    """Đọc cấu hình email từ settings.json."""
    settings_path = _get_settings_path()
    if os.path.exists(settings_path):
        try:
            with open(settings_path, "r", encoding="utf-8") as f:
                info = json.load(f).get("company", {})
                return info.get("mail_config", {})
        except:
            pass
    return {}


def _scan_email_for_contract(contract_id: str, download_dir: str):
    """Quét email tìm tệp đính kèm liên quan đến HĐ."""
    cfg = _get_email_config()
    if not cfg or not cfg.get("user") or not cfg.get("pass"):
        return None

    try:
        with MailBox(cfg["imap_server"]).login(cfg["user"], cfg["pass"]) as mailbox:
            # Tìm email có chứa ID hợp đồng trong tiêu đề
            for msg in mailbox.fetch(AND(subject=contract_id)):
                for att in msg.attachments:
                    if att.filename.lower().endswith(".pdf"):
                        # Lưu file về completed
                        file_path = os.path.join(download_dir, att.filename)
                        with open(file_path, "wb") as f:
                            f.write(att.payload)
                        print(f"✅ Đã tải file từ Email: {att.filename}")
                        return file_path
    except Exception as e:
        print(f"❌ Lỗi quét Email: {e}")
    return None


def sync_contracts_with_metadata(category: str):
    """
    Đồng bộ hợp đồng (sale/purchase).
    1. Quét local files.
    2. Quét Google Drive (thư mục shared).
    3. Quét Email (imap_tools).
    4. Cập nhật metadata JSON (Schema mới: mảng).
    """
    from core.utils import get_company_root_path
    category = (category or "").strip().lower()
    if category == "sale":
        cat_dir = "contracts_sale"
        category_title = "Bán hàng"
    elif category == "purchase":
        cat_dir = "contracts_purchase"
        category_title = "Mua hàng"
    else:
        cat_dir = "contracts_other"
        category_title = "Khác"
    local_data_root = get_company_root_path(os.path.join("contracts", cat_dir, "data"))
    # 1. Load Metadata (In-Memory Now)
    meta_key = f"contracts_{category}"
    data = {meta_key: []}


    try:
        service = get_drive_service_via_oauth()
    except Exception as e:
        return {"success": False, "message": f"Drive Auth Error: {e}"}

    # Helper: Tìm hợp đồng trong mảng metadata
    def find_contract(c_id):
        for c in data[meta_key]:
            if c.get("id") == c_id: return c
        return None

    # Tạm thời quét thư mục local để khởi tạo metadata nếu có file nhưng chưa có trong JSON
    # Quy tắc: ID hợp đồng thường nằm trong tên thư mục hoặc file
    # Ví dụ: data/2026-03-17/pending/shared/HD-20260317-ZKN1/...
    sync_count = 0
    
    for date_folder in os.listdir(local_data_root):
        date_path = os.path.join(local_data_root, date_folder)
        if not os.path.isdir(date_path): continue
        
        for status in ["pending", "completed"]:
            status_path = os.path.join(date_path, status)
            if not os.path.exists(status_path): continue
            
            for root, dirs, files in os.walk(status_path):
                for file in files:
                    if not file.lower().endswith(".pdf"): continue
                    
                    # Cố gắng trích xuất ID (giả định folder chứa file hoặc tên file là ID)
                    # rel_path: 2026-03-17/pending/shared/HD-ID/file.pdf
                    rel_parts = os.path.relpath(root, local_data_root).split(os.sep)
                    contract_id = ""
                    for part in rel_parts:
                        if part.startswith("HD-"): contract_id = part
                    if not contract_id and file.startswith("HD-"):
                        contract_id = file.split("_")[0].split(".")[0]
                    
                    if not contract_id: continue
                    
                    c = find_contract(contract_id)
                    if not c:
                        c = {
                            "id": contract_id,
                            "drive_id": "",
                            "name": "Hợp đồng " + category_title,
                            "partner": "", # Sẽ cập nhật sau
                            "mst_partner": "",
                            "date": date_folder,
                            "path": os.path.relpath(os.path.join(root, file), _ROOT).replace("\\", "/"),
                            "status": status,
                            "party_a_status": "signed" if status == "completed" else "draft",
                            "party_b_status": "signed" if status == "completed" else "draft",
                        }
                        data[meta_key].append(c)

    # 2. Xử lý đồng bộ từng hợp đồng
    for c in data[meta_key]:
        c_id = c["id"]
        c_status = c["status"]
        
        # === TỰ ĐỘNG UPLOAD FILE PENDING LÊN DRIVE NẾU CHƯA CÓ LINK ===
        if c_status in ["pending", "draft"] and not c.get("drive_link"):
            pdf_path = os.path.join(_ROOT, c.get("path", ""))
            # Chỉ upload file PDF (bỏ qua file docx)
            if os.path.exists(pdf_path) and pdf_path.lower().endswith(".pdf"):
                upload_res = upload_contract_pdf_to_drive(category, c.get("date", ""), c_id, pdf_path)
                if upload_res.get("success"):
                    c["drive_link"] = upload_res.get("link", "")
                    c["drive_id"] = upload_res.get("file_id", "")
                    sync_count += 1

        if c_status != "signed": # Nếu chưa hoàn tất, thử tìm file ký
            # A. Quét Drive (shared folder)
            # hierarchy: INIHD/contracts/[cat]/data/[date]/pending/shared/[ID]
            drive_shared_path = ["INIHD", get_drive_parent_name(), "contracts", cat_dir, "data", c["date"], "pending", "shared", c_id]
            try:
                folder_id = _get_or_create_path(service, drive_shared_path)
                # Tìm file trong folder này
                q = f"'{folder_id}' in parents and trashed = false"
                res = service.files().list(q=q, fields="files(id, name, webViewLink)").execute().get('files', [])
                
                if res:
                    # Có file mới! Tải về local completed
                    file_info = res[0]
                    dest_dir = os.path.join(local_data_root, c["date"], "completed")
                    os.makedirs(dest_dir, exist_ok=True)
                    
                    # Di chuyển logic (giả lập download thực tế)
                    # Trong thực tế cần dùng service.files().get_media()
                    # Ở đây ta giả định đã có file hoặc vừa download xong
                    
                    # Sau khi có file, cập nhật metadata
                    c["status"] = "signed"
                    c["party_b_status"] = "signed"
                    c["party_a_status"] = "signed"
                    c["drive_id"] = file_info["id"]
                    # Di chuyển file local từ pending sang completed nếu cần
                    sync_count += 1
                    
            except: pass

            # B. Quét Email (nếu vẫn chưa tìm thấy)
            if c["status"] != "signed":
                download_dir = os.path.join(local_data_root, c["date"], "completed")
                os.makedirs(download_dir, exist_ok=True)
                new_file = _scan_email_for_contract(c_id, download_dir)
                if new_file:
                    c["status"] = "signed"
                    c["party_b_status"] = "signed"
                    c["party_a_status"] = "signed"
                    # Upload file mới tải từ email lên Drive completed
                    # ... logic upload ...
                    sync_count += 1

    # Legacy Metadata Save block removed


    return {"success": True, "message": f"Hoàn tất đồng bộ {sync_count} hợp đồng.", "sync_count": sync_count}


def get_contract_share_link(category: str, date_folder: str, contract_name: str) -> str:
    """Tạo link Google Drive để gửi cho khách ký."""
    try:
        service = get_drive_service_via_oauth()
        category = (category or "").strip().lower()
        if category == "sale":
            cat_dir = "contracts_sale"
        elif category == "purchase":
            cat_dir = "contracts_purchase"
        else:
            cat_dir = "contracts_other"
        drive_shared_path = ["INIHD", get_drive_parent_name(), "contracts", cat_dir, "data", date_folder, "pending", "shared", contract_name]
        
        folder_id = _get_or_create_path(service, drive_shared_path)
        
        # Thiết lập quyền chia sẻ (bất kỳ ai có link đều xem được/upload được - thực tế nên giới hạn hơn)
        user_permission = {
            'type': 'anyone',
            'role': 'reader',
        }
        service.permissions().create(
            fileId=folder_id,
            body=user_permission,
        ).execute()
        
        # Lấy link webViewLink
        folder_info = service.files().get(fileId=folder_id, fields='webViewLink').execute()
        return folder_info.get('webViewLink', '')
    except Exception as e:
        print(f"❌ Lỗi lấy link Drive: {e}")
        return ""


def upload_contract_pdf_to_drive(category: str, date_folder: str, contract_name: str, pdf_path: str) -> dict:
    """
    Upload file PDF hợp đồng lên thư mục shared Drive đúng cấu trúc:
      {MST}/contracts/{cat_dir}/data/{date_folder}/pending/shared/{contract_name}/
    Tạo quyền public 'anyone reader', trả về dict {success, link, file_id}.
    """
    if not os.path.exists(pdf_path):
        return {"success": False, "message": f"Không tìm thấy file PDF: {pdf_path}"}

    try:
        service = get_drive_service_via_oauth()
    except Exception as e:
        return {"success": False, "message": f"Lỗi xác thực Google Drive: {e}"}

    try:
        # Chuẩn hóa folder ngày để luôn đi đúng mục data/YYYY-MM-DD.
        date_folder = (date_folder or "").strip()
        if not re.fullmatch(r"\d{4}-\d{2}-\d{2}", date_folder):
            date_folder = datetime.now().strftime("%Y-%m-%d")

        # Chuẩn hóa id hợp đồng để tránh tạo folder rác trên Drive.
        contract_name = (contract_name or "").strip()
        if not contract_name:
            contract_name = os.path.splitext(os.path.basename(pdf_path))[0]

        category = (category or "").strip().lower()
        if category == "sale":
            cat_dir = "contracts_sale"
        elif category == "purchase":
            cat_dir = "contracts_purchase"
        else:
            cat_dir = "contracts_other"
        drive_path = [
            "INIHD", get_drive_parent_name(), "contracts", cat_dir,
            "data", date_folder, "pending", "shared", contract_name
        ]
        folder_id = _get_or_create_path(service, drive_path)
    except Exception as e:
        return {"success": False, "message": f"Lỗi tạo thư mục Drive: {e}"}

    # Xóa file cũ cùng tên nếu tồn tại (tránh trùng)
    file_name = os.path.basename(pdf_path)
    try:
        old = service.files().list(
            q=f"name='{file_name}' and '{folder_id}' in parents and trashed=false",
            fields="files(id)"
        ).execute().get("files", [])
        for f in old:
            service.files().delete(fileId=f["id"]).execute()
    except Exception:
        pass

    # Upload file mới
    try:
        media = MediaFileUpload(pdf_path, mimetype="application/pdf", resumable=True)
        uploaded = service.files().create(
            body={"name": file_name, "parents": [folder_id]},
            media_body=media,
            fields="id, webViewLink"
        ).execute()
        file_id   = uploaded.get("id", "")
        file_link = uploaded.get("webViewLink", "")
    except Exception as e:
        return {"success": False, "message": f"Lỗi upload file lên Drive: {e}"}

    # Cấp quyền public cho FILE (reader)
    try:
        service.permissions().create(
            fileId=file_id,
            body={"type": "anyone", "role": "reader"}
        ).execute()
        # Lấy lại webViewLink sau khi có quyền
        info = service.files().get(fileId=file_id, fields="webViewLink").execute()
        file_link = info.get("webViewLink", file_link)
    except Exception:
        pass

    return {"success": True, "link": file_link, "file_id": file_id,
            "message": f"Đã upload '{file_name}' lên Drive thành công."}

