"""
Fizko MCP Server

Exposes Fizko API as structured, typed MCP tools for Claude Code.

Usage:
    fizko-mcp                                          # stdio (default)
    fizko-mcp --api-key=KEY --company-id=UUID           # with explicit config
    FIZKO_API_KEY=KEY FIZKO_COMPANY_ID=UUID fizko-mcp   # via env vars

Environment:
    FIZKO_API_URL        - Django API base URL (default: https://api.fizko.ai)
    FIZKO_API_KEY        - API key for authentication
    FIZKO_COMPANY_ID     - Active company UUID
    FIZKO_ALLOWED_TOOLS  - Comma-separated list of allowed tool names (empty = all)
"""

import os
import json
import calendar
import argparse
from datetime import date
from typing import Optional

import httpx
from fastmcp import FastMCP

mcp = FastMCP(
    "fizko",
    instructions=(
        "Fizko API tools for Chilean tax and accounting data. "
        "All tools that list data support pagination via limit/offset. "
        "Company is resolved automatically from the session context."
    ),
)

# Tool filtering: only expose tools listed in FIZKO_ALLOWED_TOOLS
_ALLOWED_TOOLS_RAW = os.environ.get("FIZKO_ALLOWED_TOOLS", "")
_ALLOWED_TOOLS = (
    set(t.strip() for t in _ALLOWED_TOOLS_RAW.split(",") if t.strip())
    if _ALLOWED_TOOLS_RAW
    else None
)

_original_tool = mcp.tool


def _filtered_tool(*args, **kwargs):
    """Wrapper that skips tool registration if not in allowed list."""
    def decorator(fn):
        if _ALLOWED_TOOLS is not None and fn.__name__ not in _ALLOWED_TOOLS:
            return fn
        return _original_tool(*args, **kwargs)(fn)
    return decorator


mcp.tool = _filtered_tool

API_URL = os.environ.get("FIZKO_API_URL", "https://api.fizko.ai").rstrip("/")
API_KEY = os.environ.get("FIZKO_API_KEY", "")
COMPANY_ID = os.environ.get("FIZKO_COMPANY_ID", "")
TIMEOUT = 30.0


def _headers() -> dict:
    return {"X-API-Key": API_KEY}


def _company() -> str:
    if not COMPANY_ID:
        raise ValueError("No active company. Set FIZKO_COMPANY_ID or --company-id.")
    return COMPANY_ID


def _get(path: str, params: dict | None = None) -> dict:
    clean = {k: v for k, v in (params or {}).items() if v is not None}
    r = httpx.get(f"{API_URL}{path}", headers=_headers(), params=clean, timeout=TIMEOUT)
    r.raise_for_status()
    return r.json()


def _post(path: str, data: dict | None = None, params: dict | None = None) -> dict:
    clean_params = {k: v for k, v in (params or {}).items() if v is not None}
    r = httpx.post(
        f"{API_URL}{path}",
        headers={**_headers(), "Content-Type": "application/json"},
        json=data or {},
        params=clean_params,
        timeout=TIMEOUT,
    )
    r.raise_for_status()
    return r.json() if r.status_code != 204 else {"status": "ok"}


def _patch(path: str, data: dict | None = None) -> dict:
    r = httpx.patch(
        f"{API_URL}{path}",
        headers={**_headers(), "Content-Type": "application/json"},
        json=data or {},
        timeout=TIMEOUT,
    )
    r.raise_for_status()
    return r.json()


def _post_form(path: str, data: dict, files: list[tuple] | None = None, params: dict | None = None) -> dict:
    clean_params = {k: v for k, v in (params or {}).items() if v is not None}
    r = httpx.post(
        f"{API_URL}{path}", headers=_headers(),
        data=data, files=files, params=clean_params, timeout=60.0,
    )
    r.raise_for_status()
    return r.json()


# ═══════════════════════════════════════════════════════════════════════════
# TAX
# ═══════════════════════════════════════════════════════════════════════════

@mcp.tool()
def tax_summary(period: str) -> dict:
    """Resumen tributario de un período (desde F29). Período formato YYYY-MM."""
    return _get("/api/tax/summary", {"company_id": _company(), "period": period})

@mcp.tool()
def tax_iva(period: str) -> dict:
    """Resumen de IVA para un período. Período formato YYYY-MM."""
    return _get("/api/tax/summary/iva", {"company_id": _company(), "period": period})

@mcp.tool()
def tax_timeline() -> dict:
    """Línea de tiempo tributaria (últimos 12 meses)."""
    return _get("/api/tax/summary/timeline", {"company_id": _company()})

@mcp.tool()
def tax_purchases(period_year: Optional[str] = None, period_month: Optional[str] = None, document_type: Optional[str] = None, page: Optional[int] = None) -> dict:
    """Listar facturas de compra. document_type: 33=factura, 34=exenta, 61=nota crédito."""
    return _get("/api/tax/purchases", {"company": _company(), "period_year": period_year, "period_month": period_month, "document_type": document_type, "page": page})

@mcp.tool()
def tax_purchase(id: str) -> dict:
    """Detalle de una factura de compra."""
    return _get(f"/api/tax/purchases/{id}")

@mcp.tool()
def tax_sales(period_year: Optional[str] = None, period_month: Optional[str] = None, document_type: Optional[str] = None, page: Optional[int] = None) -> dict:
    """Listar facturas de venta."""
    return _get("/api/tax/sales", {"company": _company(), "period_year": period_year, "period_month": period_month, "document_type": document_type, "page": page})

@mcp.tool()
def tax_sale(id: str) -> dict:
    """Detalle de una factura de venta."""
    return _get(f"/api/tax/sales/{id}")

@mcp.tool()
def tax_documents(period: Optional[str] = None, start_date: Optional[str] = None, end_date: Optional[str] = None, limit: Optional[int] = None, offset: Optional[int] = None) -> dict:
    """Listar todos los documentos tributarios (compras + ventas + honorarios)."""
    return _get("/api/tax/documents", {"company_id": _company(), "period": period, "start_date": start_date, "end_date": end_date, "limit": limit, "offset": offset})

@mcp.tool()
def tax_document(id: str) -> dict:
    """Detalle de un documento tributario."""
    return _get(f"/api/tax/documents/{id}")

@mcp.tool()
def tax_documents_summary() -> dict:
    """Resumen de documentos por tipo (últimos 3 meses)."""
    return _get("/api/tax/documents/summary", {"company_id": _company()})

@mcp.tool()
def tax_contacts(search: Optional[str] = None, is_supplier: Optional[str] = None, is_customer: Optional[str] = None, page: Optional[int] = None) -> dict:
    """Listar contactos tributarios (proveedores y clientes)."""
    return _get("/api/tax/contacts", {"company": _company(), "search": search, "is_supplier": is_supplier, "is_customer": is_customer, "page": page})

@mcp.tool()
def tax_contact(id: str) -> dict:
    """Detalle de un contacto con últimos documentos."""
    return _get(f"/api/tax/contacts/{id}")

@mcp.tool()
def tax_f29(year: Optional[str] = None, period: Optional[str] = None, status: Optional[str] = None, page: Optional[int] = None) -> dict:
    """Listar declaraciones F29. status: draft|submitted|confirmed|cancelled."""
    return _get("/api/tax/form29", {"company_id": _company(), "period_year": year, "period": period, "status": status, "page": page})

@mcp.tool()
def tax_f29_codes(period: str) -> dict:
    """Listar códigos F29 exportables para un período YYYY-MM."""
    y, m = period.split("-")
    return _get("/api/tax/f29/exportable-codes", {"company_id": _company(), "year": y, "month": m})

@mcp.tool()
def tax_f29_export(code: str, period: str) -> dict:
    """Exportar documentos de un código F29 como CSV. code ej: 502, 520, 538."""
    y, m = period.split("-")
    return _get("/api/tax/f29/export-csv", {"company_id": _company(), "code": code, "year": y, "month": m})

@mcp.tool()
def tax_honorarios(period_year: Optional[str] = None, period_month: Optional[str] = None) -> dict:
    """Listar boletas de honorarios."""
    return _get("/api/tax/honorarios", {"company": _company(), "period_year": period_year, "period_month": period_month})

@mcp.tool()
def tax_ddjj(year: Optional[str] = None, type: Optional[str] = None) -> dict:
    """Listar declaraciones juradas. type: comunes|otras|integral|all."""
    return _get("/api/tax/ddjj", {"company_id": _company(), "periodo": year, "type": type})

@mcp.tool()
def tax_checker_tributario(period: str) -> dict:
    """Comparar libro SII vs documentos contabilizados en Fizko. Período YYYY-MM."""
    return _get("/api/tax/checker-tributario", {"company_id": _company(), "period": period})

@mcp.tool()
def tax_checker_impuestos(period: str) -> dict:
    """Comparar impuestos declarados en F29 vs contabilizados en Fizko. Período YYYY-MM."""
    return _get("/api/tax/checker-impuestos-mensuales", {"company_id": _company(), "period": period})


# ═══════════════════════════════════════════════════════════════════════════
# ACCOUNTING
# ═══════════════════════════════════════════════════════════════════════════

@mcp.tool()
def accounting_accounts(tree: bool = False) -> dict:
    """Listar plan de cuentas. tree=True para estructura de árbol."""
    params = {"company": _company()}
    if tree: params["tree"] = "1"
    return _get("/api/accounting/accounts", params)

@mcp.tool()
def accounting_journal_entries(period: Optional[str] = None, limit: Optional[int] = None, offset: Optional[int] = None) -> dict:
    """Listar asientos contables."""
    return _get("/api/accounting/journal-entries", {"company": _company(), "period": period, "limit": limit, "offset": offset})

@mcp.tool()
def accounting_journal_entry(entry_id: str) -> dict:
    """Obtener detalle de un asiento contable."""
    return _get(f"/api/accounting/journal-entries/{entry_id}")

@mcp.tool()
def accounting_balance_report(period_from: str, period_to: str, cost_center_id: Optional[str] = None) -> dict:
    """Balance de 8 columnas. Períodos formato YYYY-MM."""
    return _get("/api/accounting/balance-report", {"company_id": _company(), "period_from": period_from, "period_to": period_to, "cost_center_id": cost_center_id})

@mcp.tool()
def accounting_income_statement(period_from: str, period_to: str, cost_center_id: Optional[str] = None) -> dict:
    """Estado de resultados (EERR). Períodos formato YYYY-MM."""
    return _get("/api/accounting/reports/income-statement", {"company_id": _company(), "period_from": period_from, "period_to": period_to, "cost_center_id": cost_center_id})

@mcp.tool()
def accounting_classified_balance(period: Optional[str] = None, period_from: Optional[str] = None, period_to: Optional[str] = None, cost_center_id: Optional[str] = None) -> dict:
    """Balance clasificado."""
    return _get("/api/accounting/reports/classified-balance", {"company_id": _company(), "period": period, "period_from": period_from, "period_to": period_to, "cost_center_id": cost_center_id})

@mcp.tool()
def accounting_rli_balance(period_from: str, period_to: str) -> dict:
    """Balance RLI (Renta Líquida Imponible). Períodos formato YYYY-MM."""
    return _get("/api/accounting/reports/rli-balance", {"company_id": _company(), "period_from": period_from, "period_to": period_to})

@mcp.tool()
def accounting_progress(period: Optional[str] = None) -> dict:
    """Avance contable de la empresa. Período opcional YYYY-MM."""
    return _get("/api/accounting/progress", {"company_id": _company(), "period": period})

@mcp.tool()
def accounting_general_journal(period: Optional[str] = None, period_from: Optional[str] = None, period_to: Optional[str] = None, page: Optional[int] = None, page_size: Optional[int] = None, cost_center_id: Optional[str] = None) -> dict:
    """Libro diario."""
    return _get("/api/accounting/reports/general-journal", {"company_id": _company(), "period": period, "period_from": period_from, "period_to": period_to, "page": page, "page_size": page_size, "cost_center_id": cost_center_id})

@mcp.tool()
def accounting_general_ledger(period: Optional[str] = None, period_from: Optional[str] = None, period_to: Optional[str] = None, account_code: Optional[str] = None, cost_center_id: Optional[str] = None) -> dict:
    """Libro mayor. Puede filtrar por código de cuenta."""
    return _get("/api/accounting/reports/general-ledger", {"company_id": _company(), "period": period, "period_from": period_from, "period_to": period_to, "account_code": account_code, "cost_center_id": cost_center_id})

@mcp.tool()
def accounting_cash_flow(start_date: Optional[str] = None, weeks: Optional[int] = None) -> dict:
    """Proyección de flujo de caja."""
    return _get("/api/accounting/cash-flow/projection", {"company_id": _company(), "start_date": start_date, "num_weeks": weeks})

@mcp.tool()
def accounting_cash_flow_detail(tab: str, start_date: Optional[str] = None, weeks: Optional[int] = None) -> dict:
    """Detalle de flujo de caja. tab: ventas | compras | honorarios."""
    return _get("/api/accounting/cash-flow/detail", {"company_id": _company(), "tab": tab, "start_date": start_date, "num_weeks": weeks})

@mcp.tool()
def accounting_obligations(obligation_type: Optional[str] = None, status: Optional[str] = None, source_document_type: Optional[str] = None, search: Optional[str] = None, date_from: Optional[str] = None, date_to: Optional[str] = None, period: Optional[str] = None, conciliado: Optional[str] = None, contabilizado: Optional[str] = None, limit: int = 30, offset: int = 0) -> dict:
    """Listar obligaciones contables. obligation_type: payable|receivable. status: pending|partial|paid|overdue."""
    return _get("/api/accounting/obligations/", {"company": _company(), "obligation_type": obligation_type, "status": status, "source_document_type": source_document_type, "search": search, "issue_date__gte": date_from, "issue_date__lte": date_to, "period": period, "conciliado": conciliado, "contabilizado": contabilizado, "limit": limit, "offset": offset})

@mcp.tool()
def accounting_obligation(obligation_id: str) -> dict:
    """Obtener detalle de una obligación."""
    return _get(f"/api/accounting/obligations/{obligation_id}/")

@mcp.tool()
def accounting_suggest_lines(obligation_id: str) -> dict:
    """Obtener sugerencia de líneas de asiento para una obligación."""
    return _get(f"/api/accounting/obligations/{obligation_id}/suggest-lines/")

@mcp.tool()
def accounting_payments(obligation_id: str) -> dict:
    """Listar los pagos (abonos) registrados en una obligación."""
    return _get(f"/api/accounting/obligations/{obligation_id}/payments/")

@mcp.tool()
def accounting_cost_centers() -> dict:
    """Listar centros de costo disponibles."""
    return _get("/api/accounting/cost-centers/", {"company": _company()})


# ═══════════════════════════════════════════════════════════════════════════
# BANKING
# ═══════════════════════════════════════════════════════════════════════════

@mcp.tool()
def banking_movements(status: Optional[str] = None, classification_status: Optional[str] = None, type: Optional[str] = None, search: Optional[str] = None, date_from: Optional[str] = None, date_to: Optional[str] = None, period: Optional[str] = None, bank_account: Optional[str] = None, limit: int = 30, offset: int = 0) -> dict:
    """Listar movimientos bancarios. status: pending|reconciled|matched|split. type: abonos|cargos. period: YYYY-MM."""
    params: dict = {"company": _company(), "limit": limit, "offset": offset}
    if status: params["status"] = status
    if classification_status: params["classification_status"] = classification_status
    if type: params["type"] = type
    if search: params["search"] = search
    if bank_account: params["bank_account"] = bank_account
    if date_from: params["date__gte"] = date_from
    if date_to: params["date__lte"] = date_to
    if period:
        y, m = period.split("-")
        last_day = calendar.monthrange(int(y), int(m))[1]
        params["date__gte"] = f"{period}-01"
        params["date__lte"] = f"{period}-{last_day:02d}"
    return _get("/api/accounting/bank-movements/", params)

@mcp.tool()
def banking_movement(movement_id: str) -> dict:
    """Obtener detalle de un movimiento bancario."""
    return _get(f"/api/accounting/bank-movements/{movement_id}/")

@mcp.tool()
def banking_list_reconciliations(period: Optional[str] = None, limit: int = 30, offset: int = 0) -> dict:
    """Listar conciliaciones completadas."""
    return _get("/api/accounting/reconciliation/list", {"company_id": _company(), "period": period, "limit": limit, "offset": offset})

@mcp.tool()
def banking_suggestions(movement_id: str) -> dict:
    """Obtener sugerencias de conciliación para un movimiento."""
    return _get("/api/accounting/reconciliation/suggestions", {"company_id": _company(), "movement_id": movement_id})

@mcp.tool()
def banking_status(period: Optional[str] = None) -> dict:
    """Estado de conciliación de la empresa. Período opcional YYYY-MM."""
    return _get("/api/accounting/reconciliation/status", {"company_id": _company(), "period": period})


# ═══════════════════════════════════════════════════════════════════════════
# ONBOARDING
# ═══════════════════════════════════════════════════════════════════════════

@mcp.tool()
def onboarding_progress(include_private: bool = False) -> dict:
    """Ver progreso del onboarding. Por defecto solo pasos públicos (del usuario)."""
    params = {} if include_private else {"visibility": "public"}
    return _get(f"/api/onboarding/companies/{_company()}/progress", params)

@mcp.tool()
def onboarding_update_step(step_key: str, status: str, metadata: Optional[str] = None) -> dict:
    """Actualizar estado de un paso. status: not_started|in_progress|completed|skipped."""
    body: dict = {"step_key": step_key, "status": status}
    if metadata: body["metadata"] = json.loads(metadata)
    return _patch(f"/api/onboarding/companies/{_company()}/progress", body)

@mcp.tool()
def onboarding_collection() -> dict:
    """Ver documentos requeridos del onboarding (constitución, liquidaciones, etc)."""
    return _get(f"/api/onboarding/companies/{_company()}/collection")

@mcp.tool()
def onboarding_comments(step_key: Optional[str] = None) -> dict:
    """Ver comentarios del onboarding."""
    params = {"step_key": step_key} if step_key else {}
    return _get(f"/api/onboarding/companies/{_company()}/comments", params)


# ═══════════════════════════════════════════════════════════════════════════
# TICKETS
# ═══════════════════════════════════════════════════════════════════════════

@mcp.tool()
def tickets_list(status: Optional[str] = None, category: Optional[str] = None, priority: Optional[str] = None, page: Optional[int] = None) -> dict:
    """Listar tickets. status: open|in_progress|awaiting_client|resolved|closed|canceled."""
    return _get("/api/tickets", {"company": _company(), "status": status, "category": category, "priority": priority, "page": page})

@mcp.tool()
def tickets_get(ticket_id: str) -> dict:
    """Ver detalle de un ticket."""
    return _get(f"/api/tickets/{ticket_id}")

@mcp.tool()
def tickets_create(category: str, title: str, content: str, priority: str = "media") -> dict:
    """Crear ticket. category: documento_faltante|inconsistencia_datos|consulta_tributaria|otro."""
    cat_map = {"documento_faltante": "document_missing", "inconsistencia_datos": "data_inconsistency", "consulta_cliente": "client_clarification", "problema_tecnico": "technical_issue", "consulta_tributaria": "tax_consultation", "problema_remuneraciones": "payroll_issue", "error_sii": "sii_sync_error", "otro": "other"}
    pri_map = {"baja": "low", "media": "medium", "alta": "high", "urgente": "urgent"}
    return _post("/api/tickets", {"company": _company(), "category": cat_map.get(category, category), "title": title, "content": content, "priority": pri_map.get(priority, priority), "creator_type": "agent", "created_by_agent": "claude_agent"})

@mcp.tool()
def tickets_update(ticket_id: str, content: str) -> dict:
    """Agregar información a un ticket existente."""
    return _patch(f"/api/tickets/{ticket_id}", {"content": content})

@mcp.tool()
def tickets_stats() -> dict:
    """Estadísticas de tickets."""
    return _get("/api/tickets/stats", {"company": _company()})


# ═══════════════════════════════════════════════════════════════════════════
# EMAIL
# ═══════════════════════════════════════════════════════════════════════════

@mcp.tool()
def email_send(to: str, subject: str, body: str) -> dict:
    """Enviar un email desde clientes@fizko.ai. body soporta markdown."""
    return _post("/api/claude-agent/send-email", {"to": to, "subject": subject, "body": body})


# ═══════════════════════════════════════════════════════════════════════════
# EXPENSES
# ═══════════════════════════════════════════════════════════════════════════

@mcp.tool()
def expenses_list(status: Optional[str] = None, category: Optional[str] = None, date_from: Optional[str] = None, date_to: Optional[str] = None, report_id: Optional[str] = None, search: Optional[str] = None, limit: int = 30, offset: int = 0) -> dict:
    """Listar gastos. status: draft|submitted|approved|rejected|reimbursed."""
    return _get("/api/expenses", {"company_id": _company(), "status": status, "category": category, "date_from": date_from, "date_to": date_to, "report_id": report_id, "search": search, "limit": limit, "offset": offset})

@mcp.tool()
def expenses_get(expense_id: str) -> dict:
    """Obtener detalle de un gasto."""
    return _get(f"/api/expenses/{expense_id}")

@mcp.tool()
def expenses_reports(status: Optional[str] = None, search: Optional[str] = None) -> dict:
    """Listar rendiciones. status: draft|submitted|approved|rejected."""
    return _get("/api/expense-reports", {"company_id": _company(), "status": status, "search": search})

@mcp.tool()
def expenses_report(report_id: str) -> dict:
    """Obtener detalle de una rendición."""
    return _get(f"/api/expense-reports/{report_id}")

@mcp.tool()
def expenses_config() -> dict:
    """Ver configuración de gastos de la empresa."""
    return _get("/api/expense-config", {"company_id": _company()})

@mcp.tool()
def expenses_create(vendor_name: str, amount: float, expense_date: Optional[str] = None, currency: str = "CLP", category: Optional[str] = None, description: Optional[str] = None, vendor_rut: Optional[str] = None, document_type: Optional[str] = None, receipt_number: Optional[str] = None, notes: Optional[str] = None) -> dict:
    """Crear gasto. document_type: boleta_electronica|factura_afecta|factura_exenta|comprobante|otro."""
    data: dict = {"vendor_name": vendor_name, "amount": str(amount), "expense_date": expense_date or date.today().isoformat(), "currency": currency}
    if category: data["category"] = category
    if description: data["description"] = description
    if vendor_rut: data["vendor_rut"] = vendor_rut
    if document_type: data["document_type"] = document_type
    if receipt_number: data["receipt_number"] = receipt_number
    if notes: data["notes"] = notes
    return _post_form("/api/expenses", data, params={"company_id": _company()})

@mcp.tool()
def expenses_submit(expense_id: str) -> dict:
    """Enviar un gasto a aprobación."""
    return _post(f"/api/expenses/{expense_id}/submit")


# ═══════════════════════════════════════════════════════════════════════════
# COMPANIES / MEMORY
# ═══════════════════════════════════════════════════════════════════════════

@mcp.tool()
def company_memory() -> dict:
    """Leer la memoria del agente para la empresa activa."""
    return _get(f"/api/claude-agent/memory/{_company()}")

@mcp.tool()
def company_remember(text: str) -> dict:
    """Agregar o reemplazar la memoria del agente para la empresa activa."""
    return _patch(f"/api/claude-agent/memory/{_company()}", {"memory": text})


# ═══════════════════════════════════════════════════════════════════════════


def main():
    """Entry point for fizko-mcp command."""
    global API_URL, API_KEY, COMPANY_ID

    parser = argparse.ArgumentParser(description="Fizko MCP Server for Claude Code")
    parser.add_argument("--api-url", default=os.environ.get("FIZKO_API_URL", "https://api.fizko.ai"), help="Fizko API base URL")
    parser.add_argument("--api-key", default=os.environ.get("FIZKO_API_KEY", ""), help="Fizko API key")
    parser.add_argument("--company-id", default=os.environ.get("FIZKO_COMPANY_ID", ""), help="Active company UUID")
    args = parser.parse_args()

    API_URL = args.api_url.rstrip("/")
    API_KEY = args.api_key
    COMPANY_ID = args.company_id

    mcp.run(transport="stdio")


if __name__ == "__main__":
    main()
