from __future__ import annotations

import json
import runpy
import sys
from dataclasses import asdict, dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any

from e2e.utils.data_factory import EnterpriseRegistrationData


STORE_PATH = Path("runtime/e2e_accounts.json")
EXCEL_EXPORT_SCRIPT = Path("scripts/export_e2e_accounts_excel.py")


def default_store_path() -> Path:
    return STORE_PATH


def excel_export_path_for(store_path: Path) -> Path:
    suffix = "".join(store_path.suffixes)
    if suffix.endswith(".json"):
        return store_path.with_suffix(".xlsx")
    return store_path.parent / f"{store_path.name}.xlsx"


@dataclass
class E2EAccount:
    phone: str
    password: str
    enterprise_name: str
    contact_name: str
    account_type: str = "main"
    parent_phone: str = ""
    parent_enterprise_name: str = ""
    member_role: str = ""
    member_status: str = ""
    member_invite_url: str = ""
    certification_status: str = "unknown"
    wanyouji_status: str = "not_opened"
    wanzhanggui_status: str = "not_opened"
    wanzhanggui_binding_status: str = "not_bound"
    bound_wanzhanggui_enterprise: str = ""
    wanzhanggui_bindings: list[dict[str, str]] = field(default_factory=list)
    workshop_authorizations: list[dict[str, str]] = field(default_factory=list)
    dispatch_strategies: list[dict[str, str]] = field(default_factory=list)
    player_payment_account_status: str = ""
    player_payment_account_verified_by: str = ""
    merchant_payment_config_status: str = ""
    merchant_payment_config_verified_by: str = ""
    latest_auth_key: str = ""
    merchant_id: str = ""
    created_at: str = field(default_factory=lambda: datetime.now().isoformat(timespec="seconds"))
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat(timespec="seconds"))

    @classmethod
    def from_registration(cls, data: EnterpriseRegistrationData) -> "E2EAccount":
        return cls(
            phone=data.phone,
            password=data.password,
            enterprise_name=data.enterprise_name,
            contact_name=data.contact_name,
        )

    def to_registration_data(self) -> EnterpriseRegistrationData:
        return EnterpriseRegistrationData(
            phone=self.phone,
            password=self.password,
            enterprise_name=self.enterprise_name,
            contact_name=self.contact_name,
        )


class AccountStore:
    def __init__(self, path: Path | None = None):
        self.path = path or STORE_PATH

    def load(self) -> list[E2EAccount]:
        if not self.path.exists():
            return []
        raw = self.load_payload()
        accounts = []
        for item in raw.get("accounts", []):
            item.setdefault("wanzhanggui_status", "not_opened")
            item.setdefault("wanzhanggui_bindings", [])
            item.setdefault("workshop_authorizations", [])
            item.setdefault("dispatch_strategies", [])
            item.setdefault("player_payment_account_status", "")
            item.setdefault("player_payment_account_verified_by", "")
            item.setdefault("merchant_payment_config_status", "")
            item.setdefault("merchant_payment_config_verified_by", "")
            item.setdefault("account_type", "main")
            item.setdefault("parent_phone", "")
            item.setdefault("parent_enterprise_name", "")
            item.setdefault("member_role", "")
            item.setdefault("member_status", "")
            item.setdefault("member_invite_url", "")
            accounts.append(E2EAccount(**item))
        return accounts

    def load_payload(self) -> dict[str, Any]:
        if not self.path.exists():
            return {"accounts": [], "dispatched_orders": []}
        payload = json.loads(self.path.read_text(encoding="utf-8"))
        payload.setdefault("accounts", [])
        payload.setdefault("dispatched_orders", [])
        return payload

    def save(self, accounts: list[E2EAccount]) -> None:
        self.path.parent.mkdir(parents=True, exist_ok=True)
        payload = self.load_payload()
        payload["accounts"] = [asdict(account) for account in accounts]
        self.path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
        self.sync_excel()

    def save_payload(self, payload: dict[str, Any]) -> None:
        self.path.parent.mkdir(parents=True, exist_ok=True)
        payload.setdefault("accounts", [])
        payload.setdefault("dispatched_orders", [])
        self.path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
        self.sync_excel()

    def sync_excel(self) -> None:
        if self.path != STORE_PATH or not EXCEL_EXPORT_SCRIPT.exists():
            return
        try:
            runpy.run_path(str(EXCEL_EXPORT_SCRIPT), run_name="__main__")
        except Exception as exc:
            print(f"[account_store] Failed to sync Excel ledger: {exc}", file=sys.stderr)

    def upsert(self, account: E2EAccount, **updates: Any) -> E2EAccount:
        accounts = self.load()
        now = datetime.now().isoformat(timespec="seconds")
        for index, existing in enumerate(accounts):
            if (
                existing.phone == account.phone
                and existing.account_type == account.account_type
                and existing.parent_phone == account.parent_phone
            ):
                merged = existing
                for key, value in asdict(account).items():
                    if value not in ("", "unknown", "not_opened", "not_bound"):
                        setattr(merged, key, value)
                for key, value in updates.items():
                    setattr(merged, key, value)
                merged.updated_at = now
                accounts[index] = merged
                self.save(accounts)
                return merged

        for key, value in updates.items():
            setattr(account, key, value)
        account.updated_at = now
        accounts.append(account)
        self.save(accounts)
        return account

    def mark_certified(self, data: EnterpriseRegistrationData) -> E2EAccount:
        return self.upsert(E2EAccount.from_registration(data), certification_status="approved")

    def mark_wanyouji_opened(self, account: E2EAccount) -> E2EAccount:
        return self.upsert(account, certification_status="approved", wanyouji_status="opened")

    def mark_wanzhanggui_opened(self, account: E2EAccount) -> E2EAccount:
        return self.upsert(account, certification_status="approved", wanzhanggui_status="opened")

    def mark_wanzhanggui_pending(self, account: E2EAccount) -> E2EAccount:
        return self.upsert(account, certification_status="approved", wanzhanggui_status="pending")

    def mark_wanzhanggui_bound(self, account: E2EAccount, auth_key: str, bound_enterprise: str = "") -> E2EAccount:
        bindings = self.merged_bindings(account, auth_key, bound_enterprise, status="bound")
        return self.upsert(
            account,
            certification_status="approved",
            wanyouji_status="opened",
            wanzhanggui_binding_status="bound",
            latest_auth_key=auth_key,
            bound_wanzhanggui_enterprise=bound_enterprise,
            wanzhanggui_bindings=bindings,
        )

    def mark_wanzhanggui_permission_pending(
        self,
        account: E2EAccount,
        auth_key: str,
        bound_enterprise: str = "",
    ) -> E2EAccount:
        bindings = self.merged_bindings(account, auth_key, bound_enterprise, status="permission_pending")
        return self.upsert(
            account,
            certification_status="approved",
            wanyouji_status="opened",
            wanzhanggui_binding_status="permission_pending",
            latest_auth_key=auth_key,
            bound_wanzhanggui_enterprise=bound_enterprise,
            wanzhanggui_bindings=bindings,
        )

    def merged_bindings(
        self,
        account: E2EAccount,
        auth_key: str,
        bound_enterprise: str,
        status: str,
    ) -> list[dict[str, str]]:
        bindings = [dict(item) for item in account.wanzhanggui_bindings]
        if account.bound_wanzhanggui_enterprise and not any(
            item.get("phone") == account.bound_wanzhanggui_enterprise for item in bindings
        ):
            bindings.append(
                {
                    "phone": account.bound_wanzhanggui_enterprise,
                    "auth_key": account.latest_auth_key,
                    "status": account.wanzhanggui_binding_status,
                }
            )

        for item in bindings:
            if item.get("phone") == bound_enterprise:
                item.update({"auth_key": auth_key, "status": status})
                return bindings

        bindings.append({"phone": bound_enterprise, "auth_key": auth_key, "status": status})
        return bindings

    def find(self, **criteria: str) -> E2EAccount | None:
        for account in reversed(self.load()):
            if all(getattr(account, key) == value for key, value in criteria.items()):
                return account
        return None

    def find_main(self, phone: str) -> E2EAccount | None:
        return self.find(phone=phone, account_type="main")

    def find_sub(self, *, phone: str, parent_phone: str = "", member_role: str = "") -> E2EAccount | None:
        for account in reversed(self.load()):
            if account.account_type != "sub" or account.phone != phone:
                continue
            if parent_phone and account.parent_phone != parent_phone:
                continue
            if member_role and account.member_role != member_role:
                continue
            return account
        return None

    def find_certified_without_wanyouji(self) -> E2EAccount | None:
        return self.find(certification_status="approved", wanyouji_status="not_opened")

    def find_certified_without_wanzhanggui(self) -> E2EAccount | None:
        return self.find(certification_status="approved", wanzhanggui_status="not_opened")

    def find_wanzhanggui_pending(self) -> E2EAccount | None:
        return self.find(certification_status="approved", wanzhanggui_status="pending")

    def find_wanzhanggui_opened(self) -> E2EAccount | None:
        return self.find(certification_status="approved", wanzhanggui_status="opened")

    def find_opened_without_binding(self) -> E2EAccount | None:
        return self.find(
            account_type="main",
            certification_status="approved",
            wanyouji_status="opened",
            wanzhanggui_binding_status="not_bound",
        )

    def find_wanyouji_without_wanzhanggui_binding(self, wanzhanggui_phone: str) -> E2EAccount | None:
        for account in reversed(self.load()):
            if account.account_type != "main":
                continue
            if account.certification_status != "approved" or account.wanyouji_status != "opened":
                continue
            if any(item.get("phone") == wanzhanggui_phone for item in account.wanzhanggui_bindings):
                continue
            if account.bound_wanzhanggui_enterprise == wanzhanggui_phone:
                continue
            return account
        return None

    def find_bound_wanyouji_without_workshop_authorization(self, wanzhanggui_phone: str) -> E2EAccount | None:
        for account in reversed(self.load()):
            if account.account_type != "main":
                continue
            if account.certification_status != "approved" or account.wanyouji_status != "opened":
                continue
            bound = account.bound_wanzhanggui_enterprise == wanzhanggui_phone or any(
                item.get("phone") == wanzhanggui_phone and item.get("status") == "bound"
                for item in account.wanzhanggui_bindings
            )
            if not bound:
                continue
            authorized = any(
                item.get("wanzhanggui_phone") == wanzhanggui_phone and item.get("status") == "authorized"
                for item in account.workshop_authorizations
            )
            if not authorized:
                return account
        return None

    def mark_workshop_authorized(
        self,
        account: E2EAccount,
        wanzhanggui_phone: str,
        auth_key: str = "",
    ) -> E2EAccount:
        authorizations = [dict(item) for item in account.workshop_authorizations]
        for item in authorizations:
            if item.get("wanzhanggui_phone") == wanzhanggui_phone:
                item.update({"auth_key": auth_key or item.get("auth_key", ""), "status": "authorized"})
                break
        else:
            authorizations.append(
                {
                    "wanzhanggui_phone": wanzhanggui_phone,
                    "auth_key": auth_key,
                    "status": "authorized",
                }
            )
        return self.upsert(account, workshop_authorizations=authorizations)

    def find_workshop_authorized_without_strategy(
        self,
        wanzhanggui_phone: str,
        strategy_type: str,
    ) -> E2EAccount | None:
        for account in reversed(self.load()):
            if account.certification_status != "approved" or account.wanyouji_status != "opened":
                continue
            authorized = any(
                item.get("wanzhanggui_phone") == wanzhanggui_phone and item.get("status") == "authorized"
                for item in account.workshop_authorizations
            )
            if not authorized:
                continue
            has_strategy = any(
                item.get("wanzhanggui_phone") == wanzhanggui_phone
                and item.get("strategy_type") == strategy_type
                and item.get("status") == "created"
                for item in account.dispatch_strategies
            )
            if not has_strategy:
                return account
        return None

    def find_workshop_authorized_accounts_without_strategy(
        self,
        wanzhanggui_phone: str,
        strategy_type: str,
        count: int,
    ) -> list[E2EAccount]:
        matched: list[E2EAccount] = []
        for account in reversed(self.load()):
            if account.certification_status != "approved" or account.wanyouji_status != "opened":
                continue
            authorized = any(
                item.get("wanzhanggui_phone") == wanzhanggui_phone and item.get("status") == "authorized"
                for item in account.workshop_authorizations
            )
            if not authorized:
                continue
            has_strategy = any(
                item.get("wanzhanggui_phone") == wanzhanggui_phone
                and item.get("strategy_type") == strategy_type
                and item.get("status") == "created"
                for item in account.dispatch_strategies
            )
            if has_strategy:
                continue
            matched.append(account)
            if len(matched) >= count:
                return matched
        return matched

    def find_workshop_authorized_accounts(
        self,
        wanzhanggui_phone: str,
        count: int,
    ) -> list[E2EAccount]:
        matched: list[E2EAccount] = []
        accounts = sorted(self.load(), key=lambda account: account.updated_at, reverse=True)
        for account in accounts:
            if account.certification_status != "approved" or account.wanyouji_status != "opened":
                continue
            authorized = any(
                item.get("wanzhanggui_phone") == wanzhanggui_phone and item.get("status") == "authorized"
                for item in account.workshop_authorizations
            )
            if not authorized:
                continue
            matched.append(account)
            if len(matched) >= count:
                return matched
        return matched

    def mark_dispatch_strategy_created(
        self,
        account: E2EAccount,
        wanzhanggui_phone: str,
        strategy_name: str,
        strategy_type: str,
    ) -> E2EAccount:
        strategies = [dict(item) for item in account.dispatch_strategies]
        strategies.append(
            {
                "wanzhanggui_phone": wanzhanggui_phone,
                "strategy_name": strategy_name,
                "strategy_type": strategy_type,
                "status": "created",
                "created_at": datetime.now().isoformat(timespec="seconds"),
            }
        )
        return self.upsert(account, dispatch_strategies=strategies)

    def mark_order_dispatched(
        self,
        *,
        wanzhanggui_phone: str,
        source_order_no: str,
        order_title: str,
        strategy_name: str,
        strategy_type: str,
        wanyouji_phones: list[str],
    ) -> None:
        payload = self.load_payload()
        orders = [dict(item) for item in payload.get("dispatched_orders", [])]
        now = datetime.now().isoformat(timespec="seconds")
        orders.append(
            {
                "wanzhanggui_phone": wanzhanggui_phone,
                "source_order_no": source_order_no,
                "order_title": order_title,
                "strategy_name": strategy_name,
                "strategy_type": strategy_type,
                "wanyouji_phones": wanyouji_phones,
                "status": "dispatched",
                "created_at": now,
                "updated_at": now,
            }
        )
        payload["dispatched_orders"] = orders
        self.save_payload(payload)

    def mark_order_without_dispatch(
        self,
        *,
        wanzhanggui_phone: str,
        source_order_no: str,
        order_title: str,
        wanyouji_phones: list[str],
        main_order_no: str = "",
    ) -> None:
        payload = self.load_payload()
        orders = [dict(item) for item in payload.get("dispatched_orders", [])]
        now = datetime.now().isoformat(timespec="seconds")
        existing = next(
            (
                item
                for item in orders
                if item.get("wanzhanggui_phone") == wanzhanggui_phone
                and (
                    item.get("source_order_no") == source_order_no
                    or (main_order_no and item.get("main_order_no") == main_order_no)
                )
            ),
            None,
        )
        updates = {
            "wanzhanggui_phone": wanzhanggui_phone,
            "main_order_no": main_order_no,
            "source_order_no": source_order_no,
            "order_title": order_title,
            "strategy_name": "",
            "strategy_type": "未推单",
            "wanyouji_phones": wanyouji_phones,
            "status": "without_dispatch",
            "source_case": "S-FLOW-001",
            "current_flow_state": "wanzhanggui_pending_accept_without_sub_order",
            "wanzhanggui_main_status": "待接单",
            "wanzhanggui_sub_order_status": "待接单-待推单",
            "wanyouji_visibility": "not_applicable_no_sub_order",
            "available_for_cases": ["S-FLOW-002", "S-FLOW-003", "S-FLOW-004", "S-FLOW-005"],
            "consumed_by_cases": [],
            "updated_at": now,
        }
        if existing:
            existing.update(updates)
        else:
            updates["created_at"] = now
            orders.append(updates)
        payload["dispatched_orders"] = orders
        self.save_payload(payload)

    def find_latest_order_without_dispatch(self, *, wanzhanggui_phone: str) -> dict[str, Any] | None:
        orders = [
            item
            for item in self.load_payload().get("dispatched_orders", [])
            if item.get("wanzhanggui_phone") == wanzhanggui_phone
            and item.get("status") == "without_dispatch"
        ]
        if not orders:
            return None
        return sorted(orders, key=lambda item: item.get("created_at", ""), reverse=True)[0]

    def find_order_for_flow_case(
        self,
        *,
        case_id: str,
        wanzhanggui_phone: str = "",
        current_flow_state: str = "",
    ) -> dict[str, Any] | None:
        orders = []
        for item in self.load_payload().get("dispatched_orders", []):
            if wanzhanggui_phone and item.get("wanzhanggui_phone") != wanzhanggui_phone:
                continue
            if current_flow_state and item.get("current_flow_state") != current_flow_state:
                continue
            if case_id in item.get("consumed_by_cases", []):
                continue
            if case_id not in item.get("available_for_cases", []):
                continue
            orders.append(item)
        if not orders:
            return None
        return sorted(orders, key=lambda item: item.get("updated_at", item.get("created_at", "")), reverse=True)[0]

    def mark_order_consumed_by_flow_case(self, *, source_order_no: str, case_id: str, **updates: Any) -> None:
        payload = self.load_payload()
        now = datetime.now().isoformat(timespec="seconds")
        for item in payload.get("dispatched_orders", []):
            if item.get("source_order_no") != source_order_no:
                continue
            consumed = list(item.get("consumed_by_cases", []))
            if case_id not in consumed:
                consumed.append(case_id)
            item["consumed_by_cases"] = consumed
            item["updated_at"] = now
            item.update(updates)
        self.save_payload(payload)

    def find_latest_dispatched_order(
        self,
        *,
        wanzhanggui_phone: str,
        strategy_type: str = "",
    ) -> dict[str, Any] | None:
        orders = [
            item
            for item in self.load_payload().get("dispatched_orders", [])
            if item.get("wanzhanggui_phone") == wanzhanggui_phone
            and item.get("status") == "dispatched"
            and (not strategy_type or item.get("strategy_type") == strategy_type)
        ]
        if not orders:
            return None
        return sorted(orders, key=lambda item: item.get("created_at", ""), reverse=True)[0]

    def mark_order_revoked(self, *, wanzhanggui_phone: str, source_order_no: str) -> None:
        payload = self.load_payload()
        now = datetime.now().isoformat(timespec="seconds")
        for item in payload.get("dispatched_orders", []):
            if item.get("wanzhanggui_phone") == wanzhanggui_phone and item.get("source_order_no") == source_order_no:
                item["status"] = "revoked"
                item["revoked_at"] = now
                item["updated_at"] = now
        self.save_payload(payload)

    def mark_task_order_status(self, *, source_order_no: str, status: str, **updates: Any) -> None:
        payload = self.load_payload()
        now = datetime.now().isoformat(timespec="seconds")
        for item in payload.get("dispatched_orders", []):
            if item.get("source_order_no") == source_order_no:
                item["status"] = status
                item["updated_at"] = now
                item.update(updates)
        self.save_payload(payload)

    def find_permission_pending_binding(self) -> E2EAccount | None:
        return self.find(
            account_type="main",
            certification_status="approved",
            wanyouji_status="opened",
            wanzhanggui_binding_status="permission_pending",
        )

    def find_wanyouji_owner_for_member(self) -> E2EAccount | None:
        for account in reversed(self.load()):
            if (
                account.account_type == "main"
                and account.certification_status == "approved"
                and account.wanyouji_status == "opened"
            ):
                return account
        return None

    def find_wanyouji_customer_service_member(self, parent_phone: str) -> E2EAccount | None:
        for account in reversed(self.load()):
            if (
                account.account_type == "sub"
                and account.parent_phone == parent_phone
                and account.member_role == "客服"
                and account.member_status in {"registered", "approved"}
                and account.wanyouji_status == "opened"
            ):
                return account
        return None

    def mark_wanyouji_member_created(
        self,
        *,
        parent: E2EAccount,
        phone: str,
        password: str,
        role: str,
        contact_name: str,
        status: str = "invited",
        invite_url: str = "",
    ) -> E2EAccount:
        member = E2EAccount(
            phone=phone,
            password=password,
            enterprise_name=parent.enterprise_name,
            contact_name=contact_name,
            account_type="sub",
            parent_phone=parent.phone,
            parent_enterprise_name=parent.enterprise_name,
            member_role=role,
            member_status=status,
            member_invite_url=invite_url,
            certification_status="approved",
            wanyouji_status="opened",
        )
        return self.upsert(member)


def account_store() -> AccountStore:
    return AccountStore()
