import asyncio
import os
import sys
from pathlib import Path
from typing import Any, Dict, Literal

import httpx


PROJECT_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(PROJECT_ROOT))
os.chdir(PROJECT_ROOT)

from app.config import settings
from app.db import init_db
from app.integration_outbox_service import (
    claim_pending_outbox,
    recover_stale_processing_outbox,
    mark_outbox_blocked,
    mark_outbox_dry_run,
    mark_outbox_failed,
    mark_outbox_sent,
)


Outcome = Literal["processed", "skipped"]


def env_bool(name: str, default: bool = False) -> bool:
    fallback = "true" if default else "false"
    value = os.getenv(name, fallback).strip().lower()
    return value in {"true", "1", "yes", "on"}


def is_dry_run() -> bool:
    return env_bool("OUTBOX_DRY_RUN", True)


def integration_enabled(target_system: str) -> bool:
    key = f"{str(target_system or '').upper()}_OUTBOX_ENABLED"
    return env_bool(key, False)


def build_chatwoot_note(payload: Dict[str, Any]) -> str:
    event_type = payload.get("event_type", "")
    action = payload.get("action", "")
    note = payload.get("note", "")
    conversation_id = payload.get("conversation_id", "")

    return f"""🤖 ClientFlow

Evento:
{event_type}

Ação:
{action}

Nota:
{note}

Conversa:
{conversation_id}
"""


async def process_chatwoot_add_private_note(item: Dict[str, Any]) -> Outcome:
    payload = item.get("payload") or {}
    conversation_id = payload.get("conversation_id")

    if not conversation_id:
        raise RuntimeError("conversation_id em falta no payload")

    if is_dry_run():
        message = f"DRY-RUN chatwoot.add_private_note conversation_id={conversation_id}"
        print(message)
        mark_outbox_dry_run(item["id"], message)
        return "processed"

    if not settings.chatwoot_write_enabled:
        raise RuntimeError("CHATWOOT_WRITE_ENABLED=false")

    if not settings.chatwoot_base_url or not settings.chatwoot_account_id or not settings.chatwoot_api_token:
        raise RuntimeError("Configuração Chatwoot incompleta")

    url = (
        settings.chatwoot_base_url.rstrip("/")
        + f"/api/v1/accounts/{settings.chatwoot_account_id}"
        + f"/conversations/{conversation_id}/messages"
    )

    body = {
        "content": build_chatwoot_note(payload),
        "message_type": "outgoing",
        "private": True,
        "content_type": "text",
        "content_attributes": {},
    }

    headers = {
        "Content-Type": "application/json",
        "api_access_token": settings.chatwoot_api_token,
    }

    async with httpx.AsyncClient(timeout=30) as client:
        response = await client.post(url, headers=headers, json=body)

    if response.status_code >= 400:
        raise RuntimeError(f"Chatwoot error {response.status_code}: {response.text}")

    mark_outbox_sent(item["id"])
    return "processed"


async def process_mautic_add_tag(item: Dict[str, Any]) -> Outcome:
    payload = item.get("payload") or {}

    if is_dry_run():
        message = (
            "DRY-RUN mautic.add_tag "
            f"conversation_id={payload.get('conversation_id')} "
            f"tag={payload.get('tag')}"
        )
        print(message)
        mark_outbox_dry_run(item["id"], message)
        return "processed"

    from app.mautic_client import add_tag_from_outbox_payload

    add_tag_from_outbox_payload(payload)
    mark_outbox_sent(item["id"])
    return "processed"


async def process_mautic_remove_tag(item: Dict[str, Any]) -> Outcome:
    payload = item.get("payload") or {}

    if is_dry_run():
        message = (
            "DRY-RUN mautic.remove_tag "
            f"conversation_id={payload.get('conversation_id')} "
            f"tag={payload.get('tag')}"
        )
        print(message)
        mark_outbox_dry_run(item["id"], message)
        return "processed"

    from app.mautic_client import remove_tag_from_outbox_payload

    remove_tag_from_outbox_payload(payload)
    mark_outbox_sent(item["id"])
    return "processed"


async def process_packlink_create_shipment(item: Dict[str, Any]) -> Outcome:
    payload = item.get("payload") or {}
    opportunity_id = payload.get("opportunity_id")

    if is_dry_run():
        message = f"DRY-RUN packlink.create_shipment opportunity_id={opportunity_id}"
        print(message)
        mark_outbox_dry_run(item["id"], message)
        return "processed"

    if not settings.packlink_enabled:
        raise RuntimeError("PACKLINK_ENABLED=false")

    from app.packlink_service import create_shipment_from_outbox_payload

    result = await create_shipment_from_outbox_payload(payload)
    print(f"Packlink shipment created reference={result.get('reference')}")
    mark_outbox_sent(item["id"])
    return "processed"




async def process_jasmin_create_quotation(item: Dict[str, Any]) -> Outcome:
    payload = item.get("payload") or {}
    opportunity_id = payload.get("opportunity_id")

    if is_dry_run():
        message = f"DRY-RUN jasmin.create_quotation opportunity_id={opportunity_id}"
        print(message)
        mark_outbox_dry_run(item["id"], message)
        return "processed"

    if not settings.jasmin_enabled:
        raise RuntimeError("JASMIN_ENABLED=false")

    from app.jasmin_service import process_create_quotation_outbox

    result = await process_create_quotation_outbox(payload)
    print(f"Jasmin quotation created id={result.get('quotation_id')}")
    mark_outbox_sent(item["id"])
    return "processed"


async def process_jasmin_convert_invoice(item: Dict[str, Any]) -> Outcome:
    payload = item.get("payload") or {}
    opportunity_id = payload.get("opportunity_id")

    if is_dry_run():
        message = f"DRY-RUN jasmin.convert_quotation_to_invoice opportunity_id={opportunity_id}"
        print(message)
        mark_outbox_dry_run(item["id"], message)
        return "processed"

    if not settings.jasmin_enabled:
        raise RuntimeError("JASMIN_ENABLED=false")

    from app.jasmin_service import process_convert_invoice_outbox

    result = await process_convert_invoice_outbox(payload)
    print(f"Jasmin invoice created id={result.get('invoice_id')}")
    mark_outbox_sent(item["id"])
    return "processed"

async def process_item(item: Dict[str, Any]) -> Outcome:
    target_system = item.get("target_system")
    action_type = item.get("action_type")

    print(f"Processing {item['id']} {target_system}.{action_type}")

    if not integration_enabled(target_system):
        message = f"Integração desativada: {target_system}.{action_type}. Ative {str(target_system or '').upper()}_OUTBOX_ENABLED=true para processar."
        print(f"BLOCKED {message}")
        mark_outbox_blocked(item["id"], message)
        return "skipped"

    if target_system == "chatwoot" and action_type == "add_private_note":
        return await process_chatwoot_add_private_note(item)

    if target_system == ("t" + "wenty"):
        print(f"SKIP legacy external CRM outbox item {item.get('id')}: integração removida")
        return "skipped"

    if target_system == "mautic" and action_type == "add_tag":
        return await process_mautic_add_tag(item)

    if target_system == "mautic" and action_type == "remove_tag":
        return await process_mautic_remove_tag(item)

    if target_system == "packlink" and action_type == "create_shipment":
        return await process_packlink_create_shipment(item)

    if target_system == "jasmin" and action_type == "create_quotation":
        return await process_jasmin_create_quotation(item)

    if target_system == "jasmin" and action_type == "convert_quotation_to_invoice":
        return await process_jasmin_convert_invoice(item)

    if is_dry_run():
        message = f"DRY-RUN unsupported-now {target_system}.{action_type}"
        print(message)
        mark_outbox_dry_run(item["id"], message)
        return "processed"

    raise RuntimeError(f"Handler não implementado: {target_system}.{action_type}")


async def main() -> int:

    # Ensure outbox schema compatibility before claiming rows.  This makes the
    # systemd worker safe on upgraded installs where the API has not yet run
    # schema self-healing for newly selected columns such as sent_at.
    init_db()

    # Nota: as notas privadas do Chatwoot podem estar desligadas sem bloquear
    # outras integrações como Packlink, Mautic ou Jasmin. A decisão de processar
    # cada target_system fica em integration_enabled() e no handler específico.
    if os.getenv("CLIENTFLOW_DISABLE_CHATWOOT_PRIVATE_NOTES", "true").lower() in {"1", "true", "yes", "sim"}:
        print("ClientFlow Chatwoot private notes disabled by env; non-Chatwoot outbox will still run.")

    limit = int(os.getenv("OUTBOX_LIMIT", "50"))
    target_system = os.getenv("OUTBOX_TARGET_SYSTEM", "").strip() or None

    worker_id = os.getenv("OUTBOX_WORKER_ID", f"process_outbox:{os.getpid()}")

    if env_bool("OUTBOX_RECOVER_STALE_BEFORE_PROCESS", True):
        recovered = recover_stale_processing_outbox(
            mode=os.getenv("OUTBOX_STALE_RECOVERY_MODE", "manual_only"),
            actor=worker_id,
        )
        if recovered:
            print(f"Recovered stale processing outbox items: {len(recovered)}")

    items = claim_pending_outbox(
        limit=limit,
        target_system=target_system,
        lock_owner=worker_id,
    )

    print(f"Claimed outbox items: {len(items)}")
    print(f"OUTBOX_WORKER_ID={worker_id}")
    print(f"OUTBOX_DRY_RUN={is_dry_run()}")
    print(f"OUTBOX_TARGET_SYSTEM={target_system or 'all'}")

    processed = 0
    skipped = 0
    failed = 0

    for item in items:
        try:
            outcome = await process_item(item)

            if outcome == "processed":
                processed += 1
            else:
                skipped += 1

        except Exception as exc:
            failed += 1
            print(f"FAILED {item.get('id')}: {exc}")
            mark_outbox_failed(item["id"], str(exc))

    print(f"Processed: {processed}")
    print(f"Skipped: {skipped}")
    print(f"Failed: {failed}")

    return 0 if failed == 0 else 1


if __name__ == "__main__":
    raise SystemExit(asyncio.run(main()))
