"""Calculation window: start date, opening balances, coverage gaps and balances."""

from __future__ import annotations

from datetime import date, datetime, timedelta, timezone
from decimal import Decimal, InvalidOperation
import json
import sqlite3

from .db import utc_now
from . import master_data, matching


SETTING_START_DATE = "calculation_start_date"
OPENING_STATUSES = ("draft", "confirmed", "superseded", "void")
GAP_KINDS = ("head", "mid", "tail")
GAP_STATUSES = ("open", "closed_attested")
ATTESTATION_STATUSES = ("pending", "approved", "rejected")


class LockedError(ValueError):
    """Start date cannot change after a period is closed."""


class ConflictError(ValueError):
    """Revision or state conflict."""


def utc_today() -> str:
    return datetime.now(timezone.utc).date().isoformat()


def _parse_decimal(value: object) -> Decimal:
    try:
        return Decimal(str(value))
    except (InvalidOperation, TypeError):
        raise ValueError("金额格式无效。") from None


def normalize_pair(company_a: int, company_b: int) -> tuple[int, int]:
    if company_a == company_b:
        raise ValueError("两家公司不能相同。")
    return (company_a, company_b) if company_a < company_b else (company_b, company_a)


def signed_from_viewer(viewer_id: int, low_id: int, high_id: int, amount: Decimal) -> Decimal:
    return amount if viewer_id == low_id else -amount


def get_setting(connection: sqlite3.Connection, key: str) -> str | None:
    row = connection.execute(
        "SELECT value FROM system_settings WHERE key = ?", (key,)
    ).fetchone()
    return row["value"] if row is not None else None


def get_calculation_start_date(connection: sqlite3.Connection) -> str | None:
    return get_setting(connection, SETTING_START_DATE)


def has_closed_periods(connection: sqlite3.Connection) -> bool:
    row = connection.execute("SELECT 1 FROM closed_periods LIMIT 1").fetchone()
    return row is not None


def is_start_date_locked(connection: sqlite3.Connection) -> bool:
    return has_closed_periods(connection)


def set_calculation_start_date(
    connection: sqlite3.Connection,
    start_date: str,
    reason: str,
    actor: sqlite3.Row,
) -> dict[str, object]:
    start_date = master_data.validate_date(start_date, "起算日", required=True)
    reason = str(reason or "").strip()
    if len(reason) < 2:
        raise ValueError("修改起算日必须填写原因。")
    if is_start_date_locked(connection):
        raise LockedError("已有结账月份，起算日已锁定。")
    before = get_calculation_start_date(connection)
    now = utc_now()
    with connection:
        connection.execute(
            """
            INSERT INTO system_settings (key, value, updated_at, updated_by)
            VALUES (?, ?, ?, ?)
            ON CONFLICT(key) DO UPDATE SET
                value = excluded.value,
                updated_at = excluded.updated_at,
                updated_by = excluded.updated_by
            """,
            (SETTING_START_DATE, start_date, now, actor["id"]),
        )
        connection.execute(
            """
            INSERT INTO system_setting_changes (
                key, before_value, after_value,
                actor_user_id, actor_username, created_at
            ) VALUES (?, ?, ?, ?, ?, ?)
            """,
            (
                SETTING_START_DATE,
                before,
                start_date,
                actor["id"],
                actor["username"],
                now,
            ),
        )
        master_data.record_change(
            connection,
            "system_setting",
            0,
            "update_start_date",
            {"calculation_start_date": before},
            {"calculation_start_date": start_date},
            reason,
            actor,
        )
        recalculate_coverage_gaps(connection)
    return {
        "calculation_start_date": start_date,
        "locked": False,
        "previous": before,
    }


def start_date_payload(connection: sqlite3.Connection) -> dict[str, object]:
    return {
        "calculation_start_date": get_calculation_start_date(connection),
        "locked": is_start_date_locked(connection),
    }


def _current_opening_revision(
    connection: sqlite3.Connection, low_id: int, high_id: int
) -> sqlite3.Row | None:
    return connection.execute(
        """
        SELECT * FROM opening_balance_revisions
        WHERE company_id_low = ? AND company_id_high = ?
          AND status IN ('draft', 'confirmed')
        ORDER BY revision DESC
        LIMIT 1
        """,
        (low_id, high_id),
    ).fetchone()


def _next_revision(connection: sqlite3.Connection, low_id: int, high_id: int) -> int:
    row = connection.execute(
        """
        SELECT MAX(revision) AS max_rev FROM opening_balance_revisions
        WHERE company_id_low = ? AND company_id_high = ?
        """,
        (low_id, high_id),
    ).fetchone()
    return int(row["max_rev"] or 0) + 1


def create_opening_balance(
    connection: sqlite3.Connection,
    company_a: int,
    company_b: int,
    amount: object,
    reason: str,
    actor: sqlite3.Row,
    *,
    currency: str = "CNY",
    viewer_company_id: int | None = None,
) -> dict[str, object]:
    low_id, high_id = normalize_pair(company_a, company_b)
    decimal_amount = _parse_decimal(amount)
    if viewer_company_id is not None and viewer_company_id == high_id:
        decimal_amount = -decimal_amount
    reason = str(reason or "").strip()
    if len(reason) < 2:
        raise ValueError("录入期初必须填写原因。")
    existing = _current_opening_revision(connection, low_id, high_id)
    if existing is not None and existing["status"] == "draft":
        raise ConflictError("该公司对已有待确认期初，请先确认或作废后再录入。")
    if existing is not None and existing["status"] == "confirmed":
        raise ConflictError("该对公司已有确认期初，请使用修订。")
    revision = _next_revision(connection, low_id, high_id)
    now = utc_now()
    with connection:
        cursor = connection.execute(
            """
            INSERT INTO opening_balance_revisions (
                company_id_low, company_id_high, amount, currency, revision,
                status, reason, actor_user_id, actor_username, created_at
            ) VALUES (?, ?, ?, ?, ?, 'draft', ?, ?, ?, ?)
            """,
            (
                low_id,
                high_id,
                str(decimal_amount),
                currency,
                revision,
                reason,
                actor["id"],
                actor["username"],
                now,
            ),
        )
        row_id = int(cursor.lastrowid)
        master_data.record_change(
            connection,
            "opening_balance",
            row_id,
            "create_draft",
            None,
            {"company_id_low": low_id, "company_id_high": high_id, "amount": str(decimal_amount)},
            reason,
            actor,
        )
    return opening_balance_payload(connection, row_id)


def confirm_opening_balance(
    connection: sqlite3.Connection,
    revision_id: int,
    reason: str,
    actor: sqlite3.Row,
) -> dict[str, object]:
    row = connection.execute(
        "SELECT * FROM opening_balance_revisions WHERE id = ?", (revision_id,)
    ).fetchone()
    if row is None:
        raise ValueError("期初记录不存在。")
    if row["status"] != "draft":
        raise ConflictError("只能确认待确认状态的期初。")
    reason = str(reason or "").strip()
    if len(reason) < 2:
        raise ValueError("确认期初必须填写原因。")
    with connection:
        connection.execute(
            """
            UPDATE opening_balance_revisions SET status = 'confirmed', reason = ?
            WHERE id = ?
            """,
            (reason, revision_id),
        )
        master_data.record_change(
            connection,
            "opening_balance",
            revision_id,
            "confirm",
            {"status": "draft"},
            {"status": "confirmed"},
            reason,
            actor,
        )
    return opening_balance_payload(connection, revision_id)


def revise_opening_balance(
    connection: sqlite3.Connection,
    revision_id: int,
    amount: object,
    reason: str,
    actor: sqlite3.Row,
) -> dict[str, object]:
    row = connection.execute(
        "SELECT * FROM opening_balance_revisions WHERE id = ?", (revision_id,)
    ).fetchone()
    if row is None:
        raise ValueError("期初记录不存在。")
    if row["status"] != "confirmed":
        raise ConflictError("只能修订已确认期初。")
    decimal_amount = _parse_decimal(amount)
    reason = str(reason or "").strip()
    if len(reason) < 2:
        raise ValueError("修订期初必须填写原因。")
    low_id = int(row["company_id_low"])
    high_id = int(row["company_id_high"])
    revision = _next_revision(connection, low_id, high_id)
    now = utc_now()
    with connection:
        connection.execute(
            "UPDATE opening_balance_revisions SET status = 'superseded' WHERE id = ?",
            (revision_id,),
        )
        cursor = connection.execute(
            """
            INSERT INTO opening_balance_revisions (
                company_id_low, company_id_high, amount, currency, revision,
                status, reason, actor_user_id, actor_username, supersedes_id, created_at
            ) VALUES (?, ?, ?, ?, ?, 'draft', ?, ?, ?, ?, ?)
            """,
            (
                low_id,
                high_id,
                str(decimal_amount),
                row["currency"],
                revision,
                reason,
                actor["id"],
                actor["username"],
                revision_id,
                now,
            ),
        )
        new_id = int(cursor.lastrowid)
        master_data.record_change(
            connection,
            "opening_balance",
            new_id,
            "revise",
            {"amount": row["amount"], "revision_id": revision_id},
            {"amount": str(decimal_amount), "revision_id": new_id},
            reason,
            actor,
        )
    return opening_balance_payload(connection, new_id)


def void_opening_balance(
    connection: sqlite3.Connection,
    revision_id: int,
    reason: str,
    actor: sqlite3.Row,
) -> dict[str, object]:
    row = connection.execute(
        "SELECT * FROM opening_balance_revisions WHERE id = ?", (revision_id,)
    ).fetchone()
    if row is None:
        raise ValueError("期初记录不存在。")
    if row["status"] not in ("draft", "confirmed"):
        raise ConflictError("该期初已作废或已被替代。")
    reason = str(reason or "").strip()
    if len(reason) < 2:
        raise ValueError("作废期初必须填写原因。")
    with connection:
        connection.execute(
            "UPDATE opening_balance_revisions SET status = 'void', reason = ? WHERE id = ?",
            (reason, revision_id),
        )
        master_data.record_change(
            connection,
            "opening_balance",
            revision_id,
            "void",
            {"status": row["status"]},
            {"status": "void"},
            reason,
            actor,
        )
    return opening_balance_payload(connection, revision_id)


def opening_balance_payload(connection: sqlite3.Connection, revision_id: int) -> dict[str, object]:
    row = connection.execute(
        """
        SELECT r.*, cl.name AS company_low_name, ch.name AS company_high_name
        FROM opening_balance_revisions r
        JOIN companies cl ON cl.id = r.company_id_low
        JOIN companies ch ON ch.id = r.company_id_high
        WHERE r.id = ?
        """,
        (revision_id,),
    ).fetchone()
    if row is None:
        raise ValueError("期初记录不存在。")
    amount = _parse_decimal(row["amount"])
    return {
        "id": row["id"],
        "company_id_low": row["company_id_low"],
        "company_id_high": row["company_id_high"],
        "company_low_name": row["company_low_name"],
        "company_high_name": row["company_high_name"],
        "amount": str(amount),
        "currency": row["currency"],
        "revision": row["revision"],
        "status": row["status"],
        "reason": row["reason"],
        "actor_username": row["actor_username"],
        "supersedes_id": row["supersedes_id"],
        "created_at": row["created_at"],
        "direction_low": "receivable" if amount >= 0 else "payable",
    }


def list_opening_balances(connection: sqlite3.Connection) -> list[dict[str, object]]:
    rows = connection.execute(
        """
        SELECT r.id FROM opening_balance_revisions r
        JOIN (
            SELECT company_id_low, company_id_high, MAX(revision) AS max_rev
            FROM opening_balance_revisions
            WHERE status IN ('draft', 'confirmed', 'void')
            GROUP BY company_id_low, company_id_high
        ) latest ON latest.company_id_low = r.company_id_low
               AND latest.company_id_high = r.company_id_high
               AND latest.max_rev = r.revision
        WHERE r.status IN ('draft', 'confirmed', 'void')
        ORDER BY r.company_id_low, r.company_id_high
        """
    ).fetchall()
    return [opening_balance_payload(connection, row["id"]) for row in rows]


def confirmed_opening_amount(
    connection: sqlite3.Connection, low_id: int, high_id: int
) -> Decimal | None:
    row = connection.execute(
        """
        SELECT amount FROM opening_balance_revisions
        WHERE company_id_low = ? AND company_id_high = ? AND status = 'confirmed'
        ORDER BY revision DESC LIMIT 1
        """,
        (low_id, high_id),
    ).fetchone()
    if row is None:
        return None
    return _parse_decimal(row["amount"])


def _date_add(day: str, delta: int) -> str:
    return (date.fromisoformat(day) + timedelta(days=delta)).isoformat()


def _merge_intervals(intervals: list[tuple[str, str]]) -> list[tuple[str, str]]:
    if not intervals:
        return []
    sorted_intervals = sorted(intervals, key=lambda item: item[0])
    merged = [sorted_intervals[0]]
    for start, end in sorted_intervals[1:]:
        last_start, last_end = merged[-1]
        if date.fromisoformat(start) <= date.fromisoformat(_date_add(last_end, 1)):
            if date.fromisoformat(end) > date.fromisoformat(last_end):
                merged[-1] = (last_start, end)
        else:
            merged.append((start, end))
    return merged


def _account_coverage_intervals(
    connection: sqlite3.Connection, account: sqlite3.Row
) -> list[tuple[str, str]]:
    rows = connection.execute(
        """
        SELECT MIN(substr(r.transaction_at, 1, 10)) AS interval_start,
               MAX(substr(r.transaction_at, 1, 10)) AS interval_end
        FROM source_rows r
        JOIN sheet_batches s ON s.id = r.sheet_batch_id
        JOIN sheet_reviews rv ON rv.sheet_batch_id = s.id AND rv.review_status = 'confirmed'
        JOIN import_batches b ON b.id = s.import_batch_id
        WHERE b.upload_bank_account_id = ?
           OR r.own_account = ?
        GROUP BY s.id
        ORDER BY interval_start
        """,
        (account["id"], account["account_number"]),
    ).fetchall()
    return [
        (row["interval_start"], row["interval_end"])
        for row in rows
        if row["interval_start"] and row["interval_end"]
    ]


def _account_transaction_dates(
    connection: sqlite3.Connection, account: sqlite3.Row
) -> list[str]:
    intervals = _account_coverage_intervals(connection, account)
    dates: list[str] = []
    for start, end in intervals:
        current = date.fromisoformat(start)
        end_day = date.fromisoformat(end)
        while current <= end_day:
            dates.append(current.isoformat())
            current += timedelta(days=1)
    return dates


def _dates_to_intervals(dates: list[str]) -> list[tuple[str, str]]:
    if not dates:
        return []
    intervals: list[tuple[str, str]] = []
    start = dates[0]
    prev = dates[0]
    for current in dates[1:]:
        if date.fromisoformat(current) == date.fromisoformat(prev) + timedelta(days=1):
            prev = current
            continue
        intervals.append((start, prev))
        start = current
        prev = current
    intervals.append((start, prev))
    return intervals


def _detect_gaps(
    required_start: str,
    required_end: str,
    covered: list[tuple[str, str]],
) -> list[tuple[str, str, str]]:
    if required_start > required_end:
        return []
    gaps: list[tuple[str, str, str]] = []
    if not covered:
        gaps.append((required_start, required_end, "head"))
        return gaps
    merged = _merge_intervals(covered)
    first_start, first_end = merged[0]
    if required_start < first_start:
        gaps.append((required_start, _date_add(first_start, -1), "head"))
    for index in range(len(merged) - 1):
        _, left_end = merged[index]
        right_start, _ = merged[index + 1]
        gap_start = _date_add(left_end, 1)
        gap_end = _date_add(right_start, -1)
        if gap_start <= gap_end:
            gaps.append((gap_start, gap_end, "mid"))
    last_start, last_end = merged[-1]
    tail_start = _date_add(last_end, 1)
    gap_from = max(tail_start, required_start)
    if gap_from <= required_end:
        kind = "tail" if last_end >= required_start else "head"
        gaps.append((gap_from, required_end, kind))
    return gaps


def detect_account_gaps(
    connection: sqlite3.Connection,
    account: sqlite3.Row,
    *,
    start_date: str | None,
    today: str | None = None,
) -> list[tuple[str, str, str]]:
    if account["status"] != "active":
        return []
    today = today or utc_today()
    effective_from = account["effective_from"] or start_date or today
    effective_to = account["effective_to"] or today
    required_start = max(filter(None, [start_date, effective_from]))
    required_end = min(today, effective_to)
    if not master_data.is_usable(account, required_start):
        return []
    intervals = _account_coverage_intervals(connection, account)
    return _detect_gaps(required_start, required_end, intervals)


def recalculate_coverage_gaps(connection: sqlite3.Connection) -> int:
    start_date = get_calculation_start_date(connection)
    today = utc_today()
    accounts = connection.execute(
        "SELECT * FROM bank_accounts WHERE status = 'active'"
    ).fetchall()
    rebuilt = 0
    with connection:
        for account in accounts:
            connection.execute(
                """
                DELETE FROM coverage_gaps
                WHERE bank_account_id = ? AND status = 'open'
                """,
                (account["id"],),
            )
            for gap_start, gap_end, gap_kind in detect_account_gaps(
                connection, account, start_date=start_date, today=today
            ):
                connection.execute(
                    """
                    INSERT INTO coverage_gaps (
                        bank_account_id, gap_start, gap_end, gap_kind,
                        status, first_detected_at
                    ) VALUES (?, ?, ?, ?, 'open', ?)
                    ON CONFLICT(bank_account_id, gap_start, gap_end) DO UPDATE SET
                        gap_kind = excluded.gap_kind,
                        status = CASE coverage_gaps.status
                            WHEN 'closed_attested' THEN 'closed_attested'
                            ELSE 'open'
                        END
                    """,
                    (account["id"], gap_start, gap_end, gap_kind, utc_now()),
                )
                rebuilt += 1
    return rebuilt


def coverage_gap_payload(connection: sqlite3.Connection, row: sqlite3.Row) -> dict[str, object]:
    account = master_data.get_account(connection, row["bank_account_id"])
    if account is None:
        raise ValueError("账户不存在。")
    company = connection.execute(
        "SELECT name FROM companies WHERE id = ?", (account["company_id"],)
    ).fetchone()
    start = date.fromisoformat(row["gap_start"])
    end = date.fromisoformat(row["gap_end"])
    gap_days = (end - start).days + 1
    pending = connection.execute(
        """
        SELECT id FROM no_business_attestations
        WHERE bank_account_id = ?
          AND gap_start = ? AND gap_end = ?
          AND status = 'pending'
        ORDER BY id DESC LIMIT 1
        """,
        (row["bank_account_id"], row["gap_start"], row["gap_end"]),
    ).fetchone()
    return {
        "id": row["id"],
        "bank_account_id": row["bank_account_id"],
        "company_id": account["company_id"],
        "company_name": company["name"] if company else None,
        "account_number_masked": master_data.mask_account_number(account["account_number"]),
        "gap_start": row["gap_start"],
        "gap_end": row["gap_end"],
        "gap_kind": row["gap_kind"],
        "gap_days": gap_days,
        "day_count": gap_days,
        "status": row["status"],
        "first_detected_at": row["first_detected_at"],
        "pending_attestation_id": pending["id"] if pending else None,
    }


def list_coverage_gaps(
    connection: sqlite3.Connection,
    *,
    company_id: int | None = None,
    status: str | None = None,
) -> list[dict[str, object]]:
    clauses: list[str] = []
    params: list[object] = []
    if company_id is not None:
        clauses.append("ba.company_id = ?")
        params.append(company_id)
    if status is not None:
        clauses.append("g.status = ?")
        params.append(status)
    where = f"WHERE {' AND '.join(clauses)}" if clauses else ""
    rows = connection.execute(
        f"""
        SELECT g.* FROM coverage_gaps g
        JOIN bank_accounts ba ON ba.id = g.bank_account_id
        {where}
        ORDER BY g.gap_start, g.bank_account_id
        """,
        params,
    ).fetchall()
    return [coverage_gap_payload(connection, row) for row in rows]


def submit_no_business_attestation(
    connection: sqlite3.Connection,
    *,
    company_id: int,
    bank_account_id: int,
    gap_start: str,
    gap_end: str,
    reason: str,
    evidence: str | None,
    actor: sqlite3.Row,
) -> dict[str, object]:
    gap_start = master_data.validate_date(gap_start, "断档起始日", required=True)
    gap_end = master_data.validate_date(gap_end, "断档结束日", required=True)
    if gap_start > gap_end:
        raise ValueError("断档起始日不能晚于结束日。")
    reason = str(reason or "").strip()
    if len(reason) < 5:
        raise ValueError("无业务说明至少 5 个字符。")
    account = master_data.get_account(connection, bank_account_id)
    if account["company_id"] != company_id:
        raise ValueError("只能为本公司账户提交说明。")
    now = utc_now()
    with connection:
        cursor = connection.execute(
            """
            INSERT INTO no_business_attestations (
                bank_account_id, gap_start, gap_end, reason, evidence,
                submitted_by, company_id, status, created_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?)
            """,
            (
                bank_account_id,
                gap_start,
                gap_end,
                reason,
                evidence,
                actor["id"],
                company_id,
                now,
            ),
        )
        attestation_id = int(cursor.lastrowid)
    return attestation_payload(connection, attestation_id)


def review_no_business_attestation(
    connection: sqlite3.Connection,
    attestation_id: int,
    decision: str,
    review_reason: str,
    actor: sqlite3.Row,
) -> dict[str, object]:
    row = connection.execute(
        "SELECT * FROM no_business_attestations WHERE id = ?", (attestation_id,)
    ).fetchone()
    if row is None:
        raise ValueError("无业务说明不存在。")
    if row["status"] != "pending":
        raise ConflictError("该说明已审核。")
    review_reason = str(review_reason or "").strip()
    if decision not in ("approve", "reject"):
        raise ValueError("审核决定无效。")
    if len(review_reason) < 2:
        raise ValueError("审核必须填写理由。")
    status = "approved" if decision == "approve" else "rejected"
    now = utc_now()
    with connection:
        connection.execute(
            """
            UPDATE no_business_attestations
            SET status = ?, reviewed_by = ?, reviewed_at = ?, review_reason = ?
            WHERE id = ?
            """,
            (status, actor["id"], now, review_reason, attestation_id),
        )
        if status == "approved":
            # Exact match first so the attested row survives recalculate's
            # "DELETE ... status='open'" and stays closed_attested.
            connection.execute(
                """
                UPDATE coverage_gaps
                SET status = 'closed_attested'
                WHERE bank_account_id = ?
                  AND gap_start = ? AND gap_end = ?
                """,
                (row["bank_account_id"], row["gap_start"], row["gap_end"]),
            )
        recalculate_coverage_gaps(connection)
        if status == "approved":
            # Overlap match: if recalculate shifts boundaries, still close
            # any open gap that intersects the attested interval.
            connection.execute(
                """
                UPDATE coverage_gaps
                SET status = 'closed_attested'
                WHERE bank_account_id = ?
                  AND status = 'open'
                  AND gap_start <= ?
                  AND gap_end >= ?
                """,
                (row["bank_account_id"], row["gap_end"], row["gap_start"]),
            )
    return attestation_payload(connection, attestation_id)


def attestation_payload(connection: sqlite3.Connection, attestation_id: int) -> dict[str, object]:
    row = connection.execute(
        "SELECT * FROM no_business_attestations WHERE id = ?", (attestation_id,)
    ).fetchone()
    if row is None:
        raise ValueError("无业务说明不存在。")
    account = master_data.get_account(connection, row["bank_account_id"])
    if account is None:
        raise ValueError("账户不存在。")
    return {
        "id": row["id"],
        "bank_account_id": row["bank_account_id"],
        "company_id": row["company_id"],
        "gap_start": row["gap_start"],
        "gap_end": row["gap_end"],
        "reason": row["reason"],
        "evidence": row["evidence"],
        "status": row["status"],
        "review_reason": row["review_reason"],
        "reviewed_at": row["reviewed_at"],
        "created_at": row["created_at"],
        "account_number_masked": master_data.mask_account_number(account["account_number"]),
    }


def list_change_log(connection: sqlite3.Connection, limit: int = 100) -> list[dict[str, object]]:
    rows = connection.execute(
        """
        SELECT * FROM master_data_changes
        WHERE entity_type IN ('system_setting', 'opening_balance')
        ORDER BY id DESC LIMIT ?
        """,
        (limit,),
    ).fetchall()
    items: list[dict[str, object]] = []
    for row in rows:
        before = json.loads(row["before_json"]) if row["before_json"] else None
        after = json.loads(row["after_json"]) if row["after_json"] else None
        target = "起算日"
        if row["entity_type"] == "opening_balance":
            target = f"期初 #{row['entity_id']}"
        items.append(
            {
                "id": row["id"],
                "created_at": row["created_at"],
                "actor_username": row["actor_username"],
                "action": row["action"],
                "target": target,
                "before": before,
                "after": after,
                "reason": row["reason"],
            }
        )
    return items


def _pair_net_change(
    connection: sqlite3.Connection,
    viewer_id: int,
    counterparty_id: int,
    *,
    cutoff: str | None,
    start_date: str | None,
) -> Decimal:
    cutoff_where = ""
    params: list[object] = [viewer_id, counterparty_id, counterparty_id, viewer_id]
    if cutoff:
        cutoff_where = "AND substr(d.effective_at, 1, 10) <= ?"
        params.append(cutoff)
    start_where = ""
    if start_date:
        start_where = "AND substr(d.effective_at, 1, 10) >= ?"
        params.append(start_date)
    rows = connection.execute(
        f"""
        SELECT d.amount, payer.company_id AS payer_id, payee.company_id AS payee_id
        FROM eligible_intercompany_events e
        JOIN transfer_match_decisions d ON d.id = e.decision_id
        JOIN transfer_decision_participants payer ON payer.decision_id = d.id AND payer.role = 'payer'
        JOIN transfer_decision_participants payee ON payee.decision_id = d.id AND payee.role = 'payee'
        WHERE (
            (payer.company_id = ? AND payee.company_id = ?)
            OR (payer.company_id = ? AND payee.company_id = ?)
        )
        {cutoff_where}
        {start_where}
        """,
        params,
    ).fetchall()
    # 站在本公司视角：正数=应收方向。转出/垫付（payer）使应收增加。
    # 与 company_transfers.net = outflow − inflow 及页面文案口径一致。
    total = Decimal("0")
    for row in rows:
        amount = _parse_decimal(row["amount"])
        if row["payer_id"] == viewer_id:
            total += amount
        elif row["payee_id"] == viewer_id:
            total -= amount
    return total


def pair_has_confirmed_opening(
    connection: sqlite3.Connection, company_a: int, company_b: int
) -> bool:
    low_id, high_id = normalize_pair(company_a, company_b)
    return confirmed_opening_amount(connection, low_id, high_id) is not None


def compute_pair_balance(
    connection: sqlite3.Connection,
    viewer_id: int,
    counterparty_id: int,
    *,
    cutoff: str | None = None,
) -> dict[str, object]:
    cutoff = cutoff or utc_today()
    cutoff = master_data.validate_date(cutoff, "截止日", required=True)
    start_date = get_calculation_start_date(connection)
    low_id, high_id = normalize_pair(viewer_id, counterparty_id)
    opening_amount: Decimal | None = None
    if start_date and pair_has_confirmed_opening(connection, viewer_id, counterparty_id):
        stored = confirmed_opening_amount(connection, low_id, high_id)
        assert stored is not None
        opening_amount = signed_from_viewer(viewer_id, low_id, high_id, stored)
    net_change = _pair_net_change(
        connection,
        viewer_id,
        counterparty_id,
        cutoff=cutoff,
        start_date=start_date,
    )
    unresolved = matching.unresolved_amounts(connection, viewer_id, cutoff=cutoff)
    pending_total = Decimal("0")
    for row in unresolved:
        pending_total += _parse_decimal(row["amount"] or "0")
    basis = "full" if opening_amount is not None and start_date else "net_change"
    payload: dict[str, object] = {
        "viewer_company_id": viewer_id,
        "counterparty_company_id": counterparty_id,
        "effective_at": cutoff,
        "calculation_start_date": start_date,
        "net_change": str(net_change),
        "basis": basis,
        "pending_unconfirmed": str(pending_total),
        "currency": "CNY",
    }
    if basis == "full" and opening_amount is not None:
        closing = opening_amount + net_change
        payload["opening"] = str(opening_amount)
        payload["closing"] = str(closing)
        payload["opening_effective_at"] = _date_add(start_date, -1)
    return payload


def list_company_counterparties(
    connection: sqlite3.Connection, company_id: int
) -> list[int]:
    rows = connection.execute(
        """
        SELECT DISTINCT CASE
            WHEN payer.company_id = ? THEN payee.company_id
            ELSE payer.company_id
        END AS other_id
        FROM eligible_intercompany_events e
        JOIN transfer_decision_participants payer ON payer.decision_id = e.decision_id AND payer.role = 'payer'
        JOIN transfer_decision_participants payee ON payee.decision_id = e.decision_id AND payee.role = 'payee'
        WHERE payer.company_id = ? OR payee.company_id = ?
        """,
        (company_id, company_id, company_id),
    ).fetchall()
    return [int(row["other_id"]) for row in rows if row["other_id"] is not None]


def compute_company_balances(
    connection: sqlite3.Connection,
    company_id: int,
    *,
    cutoff: str | None = None,
) -> dict[str, object]:
    cutoff = cutoff or utc_today()
    start_date = get_calculation_start_date(connection)
    counterparties = list_company_counterparties(connection, company_id)
    pairs = [
        compute_pair_balance(connection, company_id, other_id, cutoff=cutoff)
        for other_id in counterparties
    ]
    complete = bool(start_date) and all(item["basis"] == "full" for item in pairs)
    return {
        "company_id": company_id,
        "cutoff": cutoff,
        "calculation_start_date": start_date,
        "basis": "full" if complete and pairs else "net_change",
        "pairs": pairs,
    }


def opening_coverage_summary(connection: sqlite3.Connection) -> dict[str, int]:
    companies = connection.execute(
        "SELECT COUNT(*) AS n FROM companies"
    ).fetchone()["n"]
    recorded = connection.execute(
        """
        SELECT COUNT(DISTINCT company_id_low || ':' || company_id_high) AS n
        FROM opening_balance_revisions WHERE status = 'confirmed'
        """
    ).fetchone()["n"]
    return {"company_count": companies, "confirmed_pair_count": recorded}
