from __future__ import annotations

from html import escape
from typing import Iterable, Sequence

from .enums import CalculationPhase, PayslipSide
from .models import PayrollResult


def results_to_paysheet_rows(results: Sequence[PayrollResult]) -> list[dict[str, object]]:
    rows: list[dict[str, object]] = []
    for result in results:
        row: dict[str, object] = {
            "Employee": result.employee.full_name,
            "Department": result.employee.department or "",
            "TIN": result.employee.tin or "",
            "Basic Salary": result.basic_pay,
            "Gross Pay": result.gross_earnings,
            "Taxable Income": result.chargeable_income,
            "NSSF Employee": result.nssf_employee,
            "PAYE": result.total_paye,
            "LST": result.lst,
            "Total Deductions": result.total_deductions,
            "Net Pay": result.net_pay,
            "NSSF Employer": result.nssf_employer,
            "Employer Cost": result.total_employer_cost,
        }
        for item in result.items:
            key = _column_name_for_item(item.category.name, item.category.payslip_side, item.category.phase)
            row[key] = item.amount
        rows.append(row)
    return rows


def render_paysheet_html(results: Sequence[PayrollResult], title: str | None = None) -> str:
    rows = results_to_paysheet_rows(results)
    if not rows:
        return "<html><body><p>No payroll data.</p></body></html>"

    static_columns = [
        "Employee",
        "Department",
        "TIN",
        "Basic Salary",
    ]
    dynamic_columns = sorted(
        {
            key
            for row in rows
            for key in row.keys()
            if key not in {
                "Employee",
                "Department",
                "TIN",
                "Basic Salary",
                "Gross Pay",
                "Taxable Income",
                "NSSF Employee",
                "PAYE",
                "LST",
                "Total Deductions",
                "Net Pay",
                "NSSF Employer",
                "Employer Cost",
            }
        }
    )
    tail_columns = [
        "Gross Pay",
        "LST",
        "Taxable Income",
        "NSSF Employee",
        "PAYE",
        "Total Deductions",
        "Net Pay",
        "NSSF Employer",
        "Employer Cost",
    ]
    columns = static_columns + dynamic_columns + tail_columns

    header_html = "".join(f"<th>{escape(column)}</th>" for column in columns)
    body_rows = []
    for row in rows:
        cells = []
        for column in columns:
            value = row.get(column, "")
            if column in {"Employee", "Department", "TIN"}:
                cells.append(f"<td>{escape(str(value or ''))}</td>")
            else:
                cells.append(f"<td class='num'>{_fmt(value)}</td>")
        body_rows.append(f"<tr>{''.join(cells)}</tr>")

    return f"""
<html>
<head>
  <meta charset='utf-8'>
  <title>{escape(title or results[0].period.label + ' Paysheet')}</title>
  <style>
    body {{ font-family: Arial, sans-serif; margin: 24px; color: #1f2933; }}
    h1 {{ font-size: 18px; margin-bottom: 12px; }}
    table {{ width: 100%; border-collapse: collapse; font-size: 12px; }}
    th, td {{ border: 1px solid #d9e2ec; padding: 8px; white-space: nowrap; }}
    th {{ background: #f5f7fa; text-align: left; position: sticky; top: 0; }}
    td.num {{ text-align: right; }}
  </style>
</head>
<body>
  <h1>{escape(title or results[0].period.label + ' Paysheet')}</h1>
  <table>
    <thead><tr>{header_html}</tr></thead>
    <tbody>{''.join(body_rows)}</tbody>
  </table>
</body>
</html>
""".strip()


def render_payslip_html(result: PayrollResult) -> str:
    earnings_rows = _render_rows(_line_rows(result.iter_side(PayslipSide.EARNINGS)))
    deduction_rows = _render_rows(
        [
            ("PAYE", result.total_paye),
            ("NSSF Employee (5%)", result.nssf_employee),
            *([("LST", result.lst)] if result.lst else []),
            *_line_rows(result.iter_side(PayslipSide.DEDUCTIONS)),
        ]
    )
    employer_rows = _render_rows(
        [
            ("NSSF Employer (10%)", result.nssf_employer),
            *_line_rows(result.iter_side(PayslipSide.EMPLOYER_CONTRIBUTIONS)),
        ]
    )
    memo_rows = _render_rows(_line_rows(result.iter_side(PayslipSide.MEMO)))

    return f"""
<html>
<head>
  <meta charset='utf-8'>
  <title>{escape(result.employee.full_name)} Payslip</title>
  <style>
    body {{ font-family: 'Trebuchet MS', Arial, sans-serif; margin: 30px; color: #1f2933; }}
    .header {{ display: flex; justify-content: space-between; margin-bottom: 24px; }}
    .meta p {{ margin: 4px 0; font-size: 13px; }}
    h1 {{ font-size: 20px; text-align: center; color: #9b1c1c; margin: 10px 0 18px; }}
    .grid {{ display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }}
    table {{ width: 100%; border-collapse: collapse; font-size: 12px; }}
    th, td {{ padding: 6px 8px; border-bottom: 1px solid #d9e2ec; }}
    th {{ text-align: left; background: #f5f7fa; }}
    td.num {{ text-align: right; }}
    .summary {{ margin-top: 20px; width: 50%; }}
    .summary td {{ border-bottom: 1px solid #bcccdc; }}
    .summary .strong td {{ font-weight: 700; border-top: 2px solid #486581; }}
  </style>
</head>
<body>
  <div class='header'>
    <div class='meta'>
      <p><strong>NAME:</strong> {escape(result.employee.full_name)}</p>
      <p><strong>TIN:</strong> {escape(result.employee.tin or '')}</p>
      <p><strong>NIN:</strong> {escape(result.employee.nin or '')}</p>
      <p><strong>PERIOD:</strong> {escape(result.period.label)}</p>
    </div>
    <div class='meta'>
      <p><strong>STAFF NO:</strong> {escape(result.employee.staff_number or '')}</p>
      <p><strong>NSSF NO:</strong> {escape(result.employee.nssf_number or '')}</p>
      <p><strong>POSITION:</strong> {escape(result.employee.position or '')}</p>
      <p><strong>DEPARTMENT:</strong> {escape(result.employee.department or '')}</p>
    </div>
  </div>

  <h1>PAYMENT SLIP</h1>

  <div class='grid'>
    <div>
      <table>
        <thead><tr><th>Gross Pay</th><th class='num'>Amount</th></tr></thead>
        <tbody>
          <tr><td>Basic Pay</td><td class='num'>{_fmt(result.basic_pay)}</td></tr>
          {earnings_rows}
          <tr><td><strong>Total</strong></td><td class='num'><strong>{_fmt(result.gross_earnings)}</strong></td></tr>
          <tr><td><strong>Chargeable Income</strong></td><td class='num'><strong>{_fmt(result.chargeable_income)}</strong></td></tr>
        </tbody>
      </table>
    </div>
    <div>
      <table>
        <thead><tr><th>Deductions</th><th class='num'>Amount</th></tr></thead>
        <tbody>
          {deduction_rows}
          <tr><td><strong>Total</strong></td><td class='num'><strong>{_fmt(result.total_deductions)}</strong></td></tr>
        </tbody>
      </table>
    </div>
  </div>

  <div class='grid' style='margin-top: 20px;'>
    <div>
      <table>
        <thead><tr><th>Employer Contributions</th><th class='num'>Amount</th></tr></thead>
        <tbody>{employer_rows}<tr><td><strong>Total</strong></td><td class='num'><strong>{_fmt(result.nssf_employer + sum(line.employer_cost_impact for line in result.iter_side(PayslipSide.EMPLOYER_CONTRIBUTIONS)))}</strong></td></tr></tbody>
      </table>
    </div>
    <div>
      <table>
        <thead><tr><th>Other Information</th><th class='num'>Amount</th></tr></thead>
        <tbody>{memo_rows or "<tr><td>None</td><td class='num'>0</td></tr>"}</tbody>
      </table>
    </div>
  </div>

  <table class='summary'>
    <tbody>
      <tr><td>Gross Pay</td><td class='num'>{_fmt(result.gross_earnings)}</td></tr>
      <tr><td>Deductions</td><td class='num'>{_fmt(result.total_deductions)}</td></tr>
      <tr class='strong'><td>Net Pay</td><td class='num'>{_fmt(result.net_pay)}</td></tr>
      <tr><td>Employer Cost</td><td class='num'>{_fmt(result.total_employer_cost)}</td></tr>
    </tbody>
  </table>
</body>
</html>
""".strip()


def _column_name_for_item(name: str, side: PayslipSide, phase: CalculationPhase) -> str:
    if side == PayslipSide.EARNINGS and phase == CalculationPhase.PRE_TAX:
        return f"Gross {name}"
    if side == PayslipSide.EARNINGS and phase == CalculationPhase.POST_TAX:
        return f"Net {name}"
    return name


def _line_rows(items: Iterable[object]) -> list[tuple[str, object]]:
    rows: list[tuple[str, object]] = []
    for item in items:
        rows.append((item.category.name, item.amount))
    return rows


def _render_rows(rows: Iterable[tuple[str, object]]) -> str:
    rendered = []
    for label, amount in rows:
        rendered.append(f"<tr><td>{escape(str(label))}</td><td class='num'>{_fmt(amount)}</td></tr>")
    return "".join(rendered)


def _fmt(value: object) -> str:
    try:
        return f"{int(value):,}"
    except (TypeError, ValueError):
        return escape(str(value or ""))
