from __future__ import annotations

import json
from datetime import datetime

from fastapi import FastAPI, HTTPException
from sqlalchemy import select

from services.shared.core import new_id, utc_now_iso
from services.shared.db import get_session
from services.shared.models import (
    CustomerCreate,
    CustomerNameUpdateIn,
    CustomerHistoryEventOut,
    CustomerHistoryOut,
    CustomerHistorySummaryOut,
    CustomerOut,
    HealthResponse,
    InteractionOut,
    TelegramThreadOut,
    VoiceLiveCallOut,
)
from services.shared.sql_init import init_sql_schema
from services.shared.sql_models import (
    AsteriskCallLinkRow,
    CallRecordingRow,
    Customer,
    CustomerExternalIdentity,
    Interaction,
    InteractionTimeline,
    TelegramMessageRow,
    TelegramThreadRow,
    VoiceAISessionRow,
    VoiceTranscriptSegmentRow,
)

app = FastAPI(title="customer-service", version="1.0.0")

init_sql_schema()

_CHANNEL_LABELS = {
    "voice": "Голос",
    "telegram": "Telegram",
    "whatsapp": "WhatsApp",
    "webchat": "Веб-чат",
    "email": "Email",
}
_INTERACTION_STATUS_LABELS = {
    "new": "новое",
    "in_progress": "в работе",
    "escalated": "эскалировано",
    "closed": "закрыто",
    "abandoned": "потеряно",
}
_TELEPHONY_STATUS_LABELS = {
    "ringing": "Звонит",
    "claimed": "Взято",
    "connected": "Соединено",
    "ended": "Завершено",
    "failed": "Ошибка",
}
_TIMELINE_ACTION_TITLES = {
    "interaction.created": "Обращение создано",
    "interaction.assigned": "Обращение назначено",
    "interaction.status_changed": "Статус обращения изменён",
    "interaction.escalated": "Обращение эскалировано",
}


def _json_list(raw: str | None) -> list:
    try:
        value = json.loads(raw or "[]")
    except json.JSONDecodeError:
        return []
    return value if isinstance(value, list) else []


def _json_dict(raw: str | None) -> dict:
    try:
        value = json.loads(raw or "{}")
    except json.JSONDecodeError:
        return {}
    return value if isinstance(value, dict) else {}


def _time_value(value: str | None) -> int:
    if not value:
        return 0
    normalized = value.strip()
    if normalized.endswith("Z"):
        normalized = f"{normalized[:-1]}+00:00"
    try:
        return int(datetime.fromisoformat(normalized).timestamp())
    except ValueError:
        return 0


def _channel_label(channel: str | None) -> str:
    return _CHANNEL_LABELS.get(channel or "", channel or "Канал")


def _interaction_status_label(status: str | None) -> str:
    return _INTERACTION_STATUS_LABELS.get(status or "", status or "неизвестно")


def _telephony_status_label(status: str | None) -> str:
    return _TELEPHONY_STATUS_LABELS.get(status or "", status or "неизвестно")


def _timeline_title(action: str) -> str:
    return _TIMELINE_ACTION_TITLES.get(action, action)


def _timeline_body(action: str, metadata: dict) -> str:
    if action == "interaction.assigned":
        assignee = metadata.get("assignee")
        return f"Обращение назначено на {assignee}." if assignee else "Обращение назначено оператору."
    if action == "interaction.status_changed":
        status = metadata.get("status")
        return f"Новый статус: {_interaction_status_label(status)}."
    if action == "interaction.escalated":
        target_queue_id = metadata.get("target_queue_id")
        return (
            f"Обращение передано в очередь {target_queue_id}."
            if target_queue_id
            else "Обращение эскалировано."
        )
    if action == "interaction.created":
        channel = metadata.get("channel")
        return f"Создано новое обращение по каналу {_channel_label(channel)}."
    return "Событие сохранено в ленте обращения."


def _to_out(row: Customer) -> CustomerOut:
    phones = _json_list(row.phones_json)
    tags = _json_list(row.tags_json)
    return CustomerOut(
        customer_id=row.customer_id,
        display_name=row.display_name,
        phones=phones,
        preferred_phone=row.preferred_phone,
        tags=tags,
        created_at=row.created_at,
    )    


def _normalized_display_name(value: str) -> str:
    return " ".join(str(value or "").split()).strip()


def _interaction_to_out(row: Interaction) -> InteractionOut:
    return InteractionOut(
        interaction_id=row.interaction_id,
        channel=row.channel,
        subject=row.subject,
        customer_id=row.customer_id,
        queue_id=row.queue_id,
        priority=row.priority,
        status=row.status,
        assigned_to=row.assigned_to,
        created_at=row.created_at,
        updated_at=row.updated_at,
    )


def _telegram_thread_to_out(row: TelegramThreadRow) -> TelegramThreadOut:
    return TelegramThreadOut(
        thread_id=row.thread_id,
        chat_id=row.chat_id,
        interaction_id=row.interaction_id,
        telegram_user_id=row.telegram_user_id,
        username=row.username,
        display_name=row.display_name,
        queue_id=row.queue_id,
        status=row.status,  # type: ignore[arg-type]
        claimed_by_user=row.claimed_by_user,
        claimed_at=row.claimed_at,
        ai_session_id=row.ai_session_id,
        ai_state=row.ai_state,  # type: ignore[arg-type]
        ai_handoff_reason=row.ai_handoff_reason,
        ai_last_model_at=row.ai_last_model_at,
        last_message_at=row.last_message_at,
        last_message_preview=row.last_message_preview,
        created_at=row.created_at,
        updated_at=row.updated_at,
    )


def _voice_call_to_out(row: AsteriskCallLinkRow, *, has_recording: bool) -> VoiceLiveCallOut:
    return VoiceLiveCallOut(
        call_id=row.call_id,
        interaction_id=row.interaction_id,
        queue_id=row.queue_id,
        queue_code=row.queue_code,
        caller_number=row.caller_number,
        caller_name=row.caller_name,
        status=row.status,
        telephony_status=row.telephony_status,  # type: ignore[arg-type]
        claimed_by_user=row.claimed_by_user,
        claimed_at=row.claimed_at,
        operator_extension=row.operator_extension,
        channel_name=row.channel_name,
        started_at=row.started_at,
        connected_at=row.connected_at,
        ended_at=row.ended_at,
        updated_at=row.updated_at,
        last_transition_at=row.ended_at or row.connected_at or row.updated_at,
        hangup_cause=None,
        terminal_action=None,
        terminal_target=None,
        voice_session_id=row.voice_session_id,
        ai_session_id=row.ai_session_id,
        ai_state=row.ai_state,  # type: ignore[arg-type]
        ai_handoff_reason=row.ai_handoff_reason,
        ai_last_model_at=row.ai_last_model_at,
        has_recording=has_recording,
    )


def _history_event_sort_key(event: CustomerHistoryEventOut) -> tuple[int, str]:
    return (_time_value(event.timestamp), event.note)


def _build_customer_history(
    customer: Customer,
    interactions: list[Interaction],
    timelines: list[InteractionTimeline],
    telegram_threads: list[TelegramThreadRow],
    telegram_messages: list[TelegramMessageRow],
    live_calls: list[AsteriskCallLinkRow],
    transcript_segments: list[VoiceTranscriptSegmentRow],
    recording_call_ids: set[str],
) -> CustomerHistoryOut:
    customer_out = _to_out(customer)
    interaction_out = [_interaction_to_out(row) for row in interactions]
    telegram_thread_out = [_telegram_thread_to_out(row) for row in telegram_threads]
    live_call_out = [_voice_call_to_out(row, has_recording=row.call_id in recording_call_ids) for row in live_calls]
    interaction_by_id = {row.interaction_id: row for row in interactions}
    thread_by_id = {row.thread_id: row for row in telegram_threads}
    events: list[CustomerHistoryEventOut] = [
        CustomerHistoryEventOut(
            timestamp=customer.created_at,
            kind="profile",
            title="Профиль клиента активен",
            body=(
                f"Контакт доступен по номеру {customer_out.phones[0]}."
                if customer_out.phones
                else "Контакт добавлен в клиентскую базу."
            ),
            note=" • ".join(filter(None, [customer.customer_id, customer_out.preferred_phone])),
        )
    ]

    for row in interactions:
        events.append(
            CustomerHistoryEventOut(
                timestamp=row.updated_at or row.created_at,
                kind=row.channel or "case",
                title=f"{_channel_label(row.channel)} • {_interaction_status_label(row.status)}",
                body=row.subject or "Обращение без темы",
                note=" • ".join(
                    filter(
                        None,
                        [
                            row.interaction_id,
                            f"оператор {row.assigned_to}" if row.assigned_to else "",
                            f"queue {row.queue_id}" if row.queue_id else "",
                        ],
                    )
                ),
                interaction_id=row.interaction_id,
            )
        )

    for row in timelines:
        interaction = interaction_by_id.get(row.interaction_id)
        metadata = _json_dict(row.metadata_json)
        events.append(
            CustomerHistoryEventOut(
                timestamp=row.timestamp,
                kind=interaction.channel if interaction else "case",
                title=_timeline_title(row.action),
                body=_timeline_body(row.action, metadata),
                note=" • ".join(
                    filter(
                        None,
                        [
                            row.interaction_id,
                            f"оператор {interaction.assigned_to}" if interaction and interaction.assigned_to else "",
                        ],
                    )
                ),
                interaction_id=row.interaction_id,
            )
        )

    for row in telegram_threads:
        events.append(
            CustomerHistoryEventOut(
                timestamp=row.last_message_at or row.updated_at or row.created_at,
                kind="telegram",
                title="Telegram диалог",
                body=row.last_message_preview or "Последнее сообщение недоступно.",
                note=" • ".join(
                    filter(
                        None,
                        [
                            row.display_name or row.username or f"chat {row.chat_id}",
                            f"оператор {row.claimed_by_user}" if row.claimed_by_user else "",
                            _interaction_status_label(row.status),
                        ],
                    )
                ),
                interaction_id=row.interaction_id,
                thread_id=row.thread_id,
            )
        )

    for row in telegram_messages:
        thread = thread_by_id.get(row.thread_id or "")
        direction = {
            "outbound": "Исходящее сообщение",
            "system": "Системное сообщение",
        }.get(row.direction, "Входящее сообщение")
        events.append(
            CustomerHistoryEventOut(
                timestamp=row.created_at,
                kind="telegram",
                title=direction,
                body=row.text or "Сообщение без текста",
                note=" • ".join(
                    filter(
                        None,
                        [
                            thread.display_name or thread.username if thread else "",
                            f"оператор {row.operator_user}" if row.operator_user else "",
                            row.delivery_status or "",
                        ],
                    )
                ),
                interaction_id=row.interaction_id,
                thread_id=row.thread_id,
            )
        )

    for row in live_calls:
        completed = row.telephony_status == "ended" or bool(row.ended_at)
        events.append(
            CustomerHistoryEventOut(
                timestamp=row.ended_at or row.connected_at or row.updated_at or row.started_at,
                kind="voice",
                title=(
                    f"Звонок • {_telephony_status_label('ended')}"
                    if completed
                    else f"Звонок • {_telephony_status_label(row.telephony_status)}"
                ),
                body=f"Номер клиента: {row.caller_number or 'не определён'}",
                note=" • ".join(
                    filter(
                        None,
                        [
                            row.call_id,
                            f"внутр. {row.operator_extension}" if row.operator_extension else "",
                            f"оператор {row.claimed_by_user}" if row.claimed_by_user else "",
                        ],
                    )
                ),
                interaction_id=row.interaction_id,
                call_id=row.call_id,
            )
        )
        if row.ai_state or row.ai_handoff_reason:
            events.append(
                CustomerHistoryEventOut(
                    timestamp=row.ai_last_model_at or row.updated_at or row.started_at,
                    kind="voice",
                    title=f"Голосовой AI • {row.ai_state or 'active'}",
                    body=row.ai_handoff_reason or "AI обновил контекст разговора для оператора.",
                    note=" • ".join(
                        filter(
                            None,
                            [
                                row.call_id,
                                f"voice {row.voice_session_id}" if row.voice_session_id else "",
                                f"ai {row.ai_session_id}" if row.ai_session_id else "",
                            ],
                        )
                    ),
                    interaction_id=row.interaction_id,
                    call_id=row.call_id,
                )
            )

    for row in transcript_segments:
        speaker = "AI" if row.speaker == "assistant" else "Клиент"
        events.append(
            CustomerHistoryEventOut(
                timestamp=row.created_at,
                kind="voice",
                title=f"Транскрипт • {speaker}",
                body=row.text or "Реплика без текста",
                note=" • ".join(filter(None, [row.call_id, f"seq {row.sequence_no}"])),
                interaction_id=row.interaction_id,
                call_id=row.call_id,
            )
        )

    events.sort(key=_history_event_sort_key, reverse=True)
    active_channels = sorted(
        {
            *[row.channel for row in interactions if row.channel],
            *(["telegram"] if telegram_threads else []),
            *(["voice"] if live_calls else []),
        }
    )
    latest_event = events[0] if events else None
    primary_thread = max(
        telegram_threads,
        key=lambda row: _time_value(row.last_message_at or row.updated_at or row.created_at),
        default=None,
    )
    summary = CustomerHistorySummaryOut(
        contact_points=len(interactions) + len(telegram_threads) + len(live_calls),
        open_cases=sum(1 for row in interactions if row.status != "closed"),
        active_channels=active_channels,
        latest_event_at=latest_event.timestamp if latest_event else None,
        latest_event_title=latest_event.title if latest_event else None,
        primary_phone=customer_out.phones[0] if customer_out.phones else customer_out.preferred_phone,
        primary_telegram_thread_id=primary_thread.thread_id if primary_thread else None,
    )
    return CustomerHistoryOut(
        customer=customer_out,
        summary=summary,
        interactions=interaction_out,
        telegram_threads=telegram_thread_out,
        live_calls=live_call_out,
        history=events[:25],
    )


@app.get("/health", response_model=HealthResponse)
def health() -> HealthResponse:
    return HealthResponse(status="ok", service="customer-service")


@app.post("/customers", response_model=CustomerOut)
def create_customer(payload: CustomerCreate) -> CustomerOut:
    if payload.preferred_phone and payload.preferred_phone not in payload.phones:
        raise HTTPException(status_code=400, detail="preferred_phone must be in phones")

    session = get_session()
    try:
        row = Customer(
            customer_id=new_id("cus"),
            display_name=payload.display_name,
            phones_json=json.dumps(payload.phones, ensure_ascii=False),
            preferred_phone=payload.preferred_phone,
            tags_json=json.dumps(payload.tags, ensure_ascii=False),
            created_at=utc_now_iso(),
        )
        session.add(row)
        session.commit()
        session.refresh(row)
        return _to_out(row)
    finally:
        session.close()


@app.get("/customers/{customer_id}", response_model=CustomerOut)
def get_customer(customer_id: str) -> CustomerOut:
    session = get_session()
    try:
        row = session.execute(select(Customer).where(Customer.customer_id == customer_id)).scalar_one_or_none()
        if not row:
            raise HTTPException(status_code=404, detail="Customer not found")
        return _to_out(row)
    finally:
        session.close()


@app.get("/customers", response_model=list[CustomerOut])
def search_customers(query: str | None = None, limit: int = 100) -> list[CustomerOut]:
    session = get_session()
    try:
        rows = session.execute(select(Customer).order_by(Customer.id.desc())).scalars().all()
        out = [_to_out(r) for r in rows]
        if query:
            q = query.strip().lower()
            out = [
                c
                for c in out
                if q in c.display_name.lower()
                or any(q in p.lower() for p in c.phones)
                or any(q in t.lower() for t in c.tags)
            ]
        return out[:limit]
    finally:
        session.close()


@app.patch("/customers/{customer_id}", response_model=CustomerOut)
def update_customer_name(customer_id: str, payload: CustomerNameUpdateIn) -> CustomerOut:
    display_name = _normalized_display_name(payload.display_name)
    if len(display_name) < 2:
        raise HTTPException(status_code=400, detail="display_name must be at least 2 chars")

    session = get_session()
    try:
        customer = session.execute(
            select(Customer).where(Customer.customer_id == customer_id)
        ).scalar_one_or_none()
        if not customer:
            raise HTTPException(status_code=404, detail="Customer not found")

        now = utc_now_iso()
        customer.display_name = display_name

        identities = session.execute(
            select(CustomerExternalIdentity).where(CustomerExternalIdentity.customer_id == customer_id)
        ).scalars().all()
        for identity in identities:
            identity.display_name_snapshot = display_name
            identity.updated_at = now

        interaction_ids = session.execute(
            select(Interaction.interaction_id).where(Interaction.customer_id == customer_id)
        ).scalars().all()
        if interaction_ids:
            live_calls = session.execute(
                select(AsteriskCallLinkRow).where(AsteriskCallLinkRow.interaction_id.in_(interaction_ids))
            ).scalars().all()
            for row in live_calls:
                row.caller_name = display_name
                row.customer_name_status = "name_obtained"
                row.customer_name_value = display_name
                row.customer_name_source = "manual"
                row.customer_name_resolved_at = now
                row.updated_at = now

            voice_sessions = session.execute(
                select(VoiceAISessionRow).where(VoiceAISessionRow.interaction_id.in_(interaction_ids))
            ).scalars().all()
            for row in voice_sessions:
                row.customer_name_status = "name_obtained"
                row.customer_name_value = display_name
                row.customer_name_source = "manual"
                row.customer_name_resolved_at = now
                row.updated_at = now

        session.commit()
        session.refresh(customer)
        return _to_out(customer)
    finally:
        session.close()


@app.get("/customers/{customer_id}/history", response_model=CustomerHistoryOut)
def customer_history(customer_id: str) -> CustomerHistoryOut:
    session = get_session()
    try:
        customer = session.execute(
            select(Customer).where(Customer.customer_id == customer_id)
        ).scalar_one_or_none()
        if not customer:
            raise HTTPException(status_code=404, detail="Customer not found")

        interactions = session.execute(
            select(Interaction)
            .where(Interaction.customer_id == customer_id)
            .order_by(Interaction.updated_at.desc(), Interaction.id.desc())
        ).scalars().all()
        interaction_ids = [row.interaction_id for row in interactions]

        timelines: list[InteractionTimeline] = []
        telegram_threads: list[TelegramThreadRow] = []
        telegram_messages: list[TelegramMessageRow] = []
        live_calls: list[AsteriskCallLinkRow] = []
        transcript_segments: list[VoiceTranscriptSegmentRow] = []
        recording_call_ids: set[str] = set()

        if interaction_ids:
            timelines = session.execute(
                select(InteractionTimeline)
                .where(InteractionTimeline.interaction_id.in_(interaction_ids))
                .order_by(InteractionTimeline.timestamp.desc(), InteractionTimeline.id.desc())
            ).scalars().all()

            telegram_threads = session.execute(
                select(TelegramThreadRow)
                .where(TelegramThreadRow.interaction_id.in_(interaction_ids))
                .order_by(TelegramThreadRow.last_message_at.desc(), TelegramThreadRow.id.desc())
            ).scalars().all()

            live_calls = session.execute(
                select(AsteriskCallLinkRow)
                .where(AsteriskCallLinkRow.interaction_id.in_(interaction_ids))
                .order_by(AsteriskCallLinkRow.updated_at.desc(), AsteriskCallLinkRow.id.desc())
            ).scalars().all()

        thread_ids = [row.thread_id for row in telegram_threads if row.thread_id]
        if thread_ids:
            telegram_messages = session.execute(
                select(TelegramMessageRow)
                .where(TelegramMessageRow.thread_id.in_(thread_ids))
                .order_by(TelegramMessageRow.created_at.desc(), TelegramMessageRow.id.desc())
                .limit(8)
            ).scalars().all()

        call_ids = [row.call_id for row in live_calls if row.call_id]
        if call_ids:
            recording_call_ids = set(
                session.execute(
                    select(CallRecordingRow.call_id).where(CallRecordingRow.call_id.in_(call_ids))
                ).scalars().all()
            )
            transcript_segments = session.execute(
                select(VoiceTranscriptSegmentRow)
                .where(
                    VoiceTranscriptSegmentRow.call_id.in_(call_ids),
                    VoiceTranscriptSegmentRow.is_final.is_(True),
                )
                .order_by(VoiceTranscriptSegmentRow.created_at.desc(), VoiceTranscriptSegmentRow.id.desc())
                .limit(8)
            ).scalars().all()

        return _build_customer_history(
            customer,
            interactions,
            timelines,
            telegram_threads,
            telegram_messages,
            live_calls,
            transcript_segments,
            recording_call_ids,
        )
    finally:
        session.close()
