"""Personal transit account mappings and their administrator approval.

A personal transit mapping states that a personal bank account represents a
company for a specific flow direction inside an effective interval. The key is
the normalized account number; the account holder name is display-only and
never resolves a counterparty on its own (``AGENTS.md``: never infer a
financial fact from a name alone). Only administrator-approved (``active``)
mappings inside their effective window participate in counterparty resolution.
"""

from __future__ import annotations

import sqlite3

from .db import utc_now
from .master_data import (
    ConflictError,
    mask_account_number,
    normalize_account_number,
    record_change,
    validate_date,
)

MAPPING_STATUSES = ("pending", "active", "returned", "disabled")
ALLOWED_DIRECTIONS = ("outgoing", "incoming", "both")


def get_mapping(connection: sqlite3.Connection, mapping_id: int) -> sqlite3.Row | None:
    return connection.execute(
        "SELECT * FROM personal_transit_mappings WHERE id = ?", (mapping_id,)
    ).fetchone()


def submit_mapping(
    connection: sqlite3.Connection,
    *,
    account_number: object,
    account_name: object,
    represented_company_id: int,
    allowed_direction: str,
    effective_from: object = None,
    actor: sqlite3.Row | None,
) -> sqlite3.Row:
    """Create a pending personal transit mapping; returns the new row.

    The account number is normalized (digits only) and unique: resubmitting a
    returned request from the same company reopens the same row as pending;
    any other existing row is a conflict guarded by the UNIQUE constraint.
    """
    number = normalize_account_number(account_number)
    holder = str(account_name or "").strip() or None
    direction = str(allowed_direction or "").strip()
    if direction not in ALLOWED_DIRECTIONS:
        raise ValueError(f"允许方向必须是：{'、'.join(ALLOWED_DIRECTIONS)}。")
    if not holder:
        raise ValueError("账户户名不能为空（仅作展示，不作匹配键）。")
    company = connection.execute(
        "SELECT id FROM companies WHERE id = ?", (represented_company_id,)
    ).fetchone()
    if company is None:
        raise ValueError("代表的公司不存在。")
    start = validate_date(effective_from, "生效日期")
    now = utc_now()

    existing = connection.execute(
        "SELECT * FROM personal_transit_mappings WHERE account_number = ?", (number,)
    ).fetchone()
    if existing is None:
        try:
            with connection:
                cursor = connection.execute(
                    """
                    INSERT INTO personal_transit_mappings (
                        account_number, account_name, represented_company_id,
                        allowed_direction, status, effective_from, submitted_by,
                        created_at, updated_at
                    ) VALUES (?, ?, ?, ?, 'pending', ?, ?, ?, ?)
                    """,
                    (
                        number, holder, represented_company_id, direction,
                        start, actor["id"] if actor else None, now, now,
                    ),
                )
        except sqlite3.IntegrityError as exc:
            raise ConflictError("该个人过账账号已登记，请等待现有申请处理。") from exc
        mapping_id = int(cursor.lastrowid)
        with connection:
            record_change(
                connection, "personal_transit_mapping", mapping_id, "submit", None,
                {"account_number": number, "account_name": holder,
                 "represented_company_id": represented_company_id,
                 "allowed_direction": direction, "status": "pending",
                 "effective_from": start},
                None, actor,
            )
        return get_mapping(connection, mapping_id)

    if existing["status"] == "returned":
        with connection:
            connection.execute(
                """
                UPDATE personal_transit_mappings
                SET account_name = ?, represented_company_id = ?,
                    allowed_direction = ?, status = 'pending', effective_from = ?,
                    submitted_by = ?, reviewed_by = NULL, reviewed_at = NULL,
                    review_reason = NULL, updated_at = ?
                WHERE id = ? AND status = 'returned'
                """,
                (
                    holder, represented_company_id, direction, start,
                    actor["id"] if actor else None, now, existing["id"],
                ),
            )
            record_change(
                connection, "personal_transit_mapping", existing["id"], "resubmit",
                None, {"account_number": number, "status": "pending",
                       "allowed_direction": direction, "effective_from": start},
                "退回后重新提交", actor,
            )
        return get_mapping(connection, existing["id"])

    raise ConflictError("该个人过账账号已登记，请等待现有申请处理。")


def review_mapping(
    connection: sqlite3.Connection,
    mapping_id: int,
    decision: str,
    reason: str | None,
    actor: sqlite3.Row,
    *,
    effective_from: object = None,
    effective_to: object = None,
) -> sqlite3.Row:
    """Approve, return or disable a mapping; returns the updated row."""
    mapping = get_mapping(connection, mapping_id)
    if mapping is None:
        raise LookupError("个人过账映射不存在。")
    reason = (reason or "").strip() or None
    today = utc_now()[:10]

    if decision == "approve":
        if mapping["status"] != "pending":
            raise ConflictError("只有待复核的映射可以审核通过。")
        start = validate_date(effective_from, "生效日期") or mapping["effective_from"] or today
        with connection:
            connection.execute(
                """
                UPDATE personal_transit_mappings
                SET status = 'active', effective_from = ?, effective_to = NULL,
                    reviewed_by = ?, reviewed_at = ?, review_reason = ?, updated_at = ?
                WHERE id = ?
                """,
                (start, actor["id"], utc_now(), reason, utc_now(), mapping_id),
            )
            record_change(
                connection, "personal_transit_mapping", mapping_id, "approve", None,
                {"status": "active", "effective_from": start}, reason, actor,
            )
    elif decision == "return":
        if mapping["status"] != "pending":
            raise ConflictError("只有待复核的映射可以退回。")
        if reason is None:
            raise ValueError("退回必须填写原因。")
        with connection:
            connection.execute(
                """
                UPDATE personal_transit_mappings
                SET status = 'returned', reviewed_by = ?, reviewed_at = ?,
                    review_reason = ?, updated_at = ?
                WHERE id = ?
                """,
                (actor["id"], utc_now(), reason, utc_now(), mapping_id),
            )
            record_change(
                connection, "personal_transit_mapping", mapping_id, "return", None,
                {"status": "returned"}, reason, actor,
            )
    elif decision == "disable":
        if mapping["status"] != "active":
            raise ConflictError("只有已启用的映射可以停用。")
        if reason is None:
            raise ValueError("停用必须填写原因。")
        end = validate_date(effective_to, "停用日期") or today
        with connection:
            connection.execute(
                """
                UPDATE personal_transit_mappings
                SET status = 'disabled', effective_to = ?,
                    reviewed_by = ?, reviewed_at = ?, review_reason = ?, updated_at = ?
                WHERE id = ?
                """,
                (end, actor["id"], utc_now(), reason, utc_now(), mapping_id),
            )
            record_change(
                connection, "personal_transit_mapping", mapping_id, "disable", None,
                {"status": "disabled", "effective_to": end}, reason, actor,
            )
    else:
        raise ValueError("审核决定必须是 approve、return 或 disable。")
    return get_mapping(connection, mapping_id)


def list_mappings(
    connection: sqlite3.Connection,
    company_id: int | None = None,
    status: str | None = None,
) -> list[sqlite3.Row]:
    conditions: list[str] = []
    params: list[object] = []
    if company_id is not None:
        conditions.append("m.represented_company_id = ?")
        params.append(company_id)
    if status is not None:
        if status not in MAPPING_STATUSES:
            raise ValueError("无效的映射状态。")
        conditions.append("m.status = ?")
        params.append(status)
    where = f"WHERE {' AND '.join(conditions)}" if conditions else ""
    return connection.execute(
        f"""
        SELECT m.*, c.name AS company_name
        FROM personal_transit_mappings m
        JOIN companies c ON c.id = m.represented_company_id
        {where}
        ORDER BY m.id
        """,
        params,
    ).fetchall()


def is_mapping_active(mapping: sqlite3.Row, on_date: str) -> bool:
    """True when the mapping may resolve a counterparty on ``on_date``.

    ``effective_to`` is the last participating day (inclusive). Disabled
    mappings stop resolving new rows but keep identifying historical rows
    inside their effective window, matching ``bank_accounts`` semantics.
    """
    if mapping["status"] not in ("active", "disabled"):
        return False
    if mapping["effective_from"] and on_date < mapping["effective_from"]:
        return False
    if mapping["effective_to"] and on_date > mapping["effective_to"]:
        return False
    return True


def direction_allowed(mapping: sqlite3.Row, row_direction: str) -> bool:
    """True when the personal account may represent the company for this flow.

    ``allowed_direction`` describes the transfer from the represented company's
    perspective: when the personal account appears as the counterparty of an
    outgoing row, the represented company is receiving (incoming); for an
    incoming row the represented company is paying out (outgoing).
    """
    counterparty_flow = "incoming" if row_direction == "outgoing" else "outgoing"
    allowed = mapping["allowed_direction"]
    return allowed == "both" or allowed == counterparty_flow


def mapping_payload(mapping: sqlite3.Row, *, full: bool) -> dict[str, object]:
    """Serialize a mapping; company-facing views only ever see masked numbers."""
    payload: dict[str, object] = {
        "id": mapping["id"],
        "account_name": mapping["account_name"],
        "represented_company_id": mapping["represented_company_id"],
        "allowed_direction": mapping["allowed_direction"],
        "status": mapping["status"],
        "effective_from": mapping["effective_from"],
        "effective_to": mapping["effective_to"],
        "reviewed_at": mapping["reviewed_at"],
        "review_reason": mapping["review_reason"],
        "created_at": mapping["created_at"],
    }
    if "company_name" in mapping.keys():
        payload["company_name"] = mapping["company_name"]
    if full:
        payload["account_number"] = mapping["account_number"]
    else:
        payload["account_number_masked"] = mask_account_number(mapping["account_number"])
    return payload
