"""JustCall Sales Dialer endpoints for Sheets add-on."""

from __future__ import annotations

import json
import logging
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends, HTTPException, Query, Response, Security
from fastapi.security import HTTPAuthorizationCredentials
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession

from ...auth_core import OrgContext, get_current_identity
from ...auth_security import api_key_hdr, http_bearer, require_account
from ...db import get_session
from ...services.justcall_adapter import (
    JustCallAdapter,
    JustCallAuthError,
    JustCallError,
    JustCallRateLimitError,
)
from ...services.justcall_models import JustCallConfig
from ...services.secret_storage import get_saas_secret_backend
from ...services.usage_gateway import UsageGateway

from g_gremlin.justcall import normalize_phone_e164

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v2/justcall", tags=["justcall"])

MAX_PREVIEW_ROWS = 500
MAX_ERRORS = 50


class ProfileInfo(BaseModel):
    id: str
    label: str
    connected_at: Optional[str] = None


class ProviderStatus(BaseModel):
    configured: bool


class ProfilesResponse(BaseModel):
    profiles: List[ProfileInfo]
    provider_status: ProviderStatus


class CampaignInfo(BaseModel):
    id: str
    name: str
    status: Optional[str] = None
    contact_count: Optional[int] = None


class CampaignsResponse(BaseModel):
    campaigns: List[CampaignInfo]


class CampaignsRequest(BaseModel):
    profile_id: str


class NumberInfo(BaseModel):
    id: str
    number: str
    label: Optional[str] = None
    sms_enabled: Optional[bool] = None
    whatsapp_enabled: Optional[bool] = None
    owner_id: Optional[str] = None
    owner_email: Optional[str] = None
    owner_name: Optional[str] = None


class NumbersResponse(BaseModel):
    numbers: List[NumberInfo]


class PreviewError(BaseModel):
    row_number: int
    field: str
    message: str


class PreviewSampleTransform(BaseModel):
    original: str
    normalized: str


class PreviewResponse(BaseModel):
    total_rows: int
    valid_rows: int
    invalid_rows: int
    skipped_rows: int
    normalized_changed: int = 0
    sample_transforms: List[PreviewSampleTransform] = Field(default_factory=list)
    errors: List[PreviewError] = Field(default_factory=list)


class JustCallRowsRequest(BaseModel):
    config: JustCallConfig
    rows: List[Dict[str, Any]] = Field(default_factory=list)


async def require_justcall_context(
    bearer: Optional[HTTPAuthorizationCredentials] = Security(http_bearer),
    api_key: Optional[str] = Security(api_key_hdr),
    db: AsyncSession = Depends(get_session),
) -> OrgContext:
    account_id = await require_account(bearer=bearer, api_key=api_key, db=db)
    user_id = "api-key"
    email = None
    if bearer:
        try:
            ident = await get_current_identity(bearer)
            user_id = ident.get("user_id") or user_id
            email = ident.get("email")
        except HTTPException:
            pass
    return OrgContext(account_id=account_id, user_id=user_id, email=email)


async def require_byo_access(org: OrgContext, db: AsyncSession) -> None:
    gateway = UsageGateway(db)
    capabilities = await gateway.get_capabilities(org.account_id)
    byo = capabilities.get("byo") or {}
    if not byo.get("enabled"):
        raise HTTPException(
            status_code=403,
            detail={
                "error": "TIER_REQUIRED",
                "message": "BYO activation requires Unleashed tier or BYO add-on",
                "current_tier": capabilities.get("tier", "free"),
                "required_tier": "unleashed_or_addon",
                "upgrade_url": "/pricing",
            },
        )


def _parse_credentials(value: str) -> Dict[str, str]:
    try:
        payload = json.loads(value or "{}")
    except json.JSONDecodeError as exc:
        raise HTTPException(status_code=400, detail="Invalid JustCall credential payload") from exc
    api_key = str(payload.get("api_key") or payload.get("apiKey") or "").strip()
    api_secret = str(payload.get("api_secret") or payload.get("apiSecret") or "").strip()
    if not api_key or not api_secret:
        raise HTTPException(status_code=400, detail="JustCall credentials missing api_key/api_secret")
    return {"api_key": api_key, "api_secret": api_secret}


def _get_row_number(row: Dict[str, Any], fallback_index: int, starting_row: int) -> int:
    for key, is_zero_based in (
        ("row_number", False),
        ("row_index", True),
        ("__fm_row_index__", True),
    ):
        if key in row:
            try:
                num = int(row.get(key))
                return num + (1 if is_zero_based else 0)
            except (TypeError, ValueError):
                continue
    return starting_row + fallback_index


def _should_skip_row(row: Dict[str, Any], campaign_id: str, writeback: Dict[str, Any]) -> bool:
    if not writeback or not writeback.get("enabled", True):
        return False
    status_col = writeback.get("status_column")
    campaign_col = writeback.get("campaign_column")
    if not status_col or not campaign_col:
        return False
    status_val = str(row.get(status_col, "")).strip().lower()
    campaign_val = str(row.get(campaign_col, "")).strip()
    return status_val == "enrolled" and campaign_val == campaign_id


@router.get("/profiles", response_model=ProfilesResponse)
async def list_profiles(
    org: OrgContext = Depends(require_justcall_context),
    db: AsyncSession = Depends(get_session),
) -> ProfilesResponse:
    await require_byo_access(org, db)

    backend = get_saas_secret_backend()
    secrets = await backend.list_secrets(db, org.account_id, integration="justcall")
    api_secrets = [s for s in secrets if s.get("key_name") == "api_key"]

    profiles: List[ProfileInfo] = []
    for secret in api_secrets:
        label_suffix = secret.get("key_suffix") or ""
        try:
            value = await backend.get_secret_by_id(
                db=db,
                secret_id=secret.get("id"),
                workspace_id=org.account_id,
                integration="justcall",
            )
            if value and value.value:
                creds = _parse_credentials(value.value)
                label_suffix = creds["api_key"][-4:]
        except HTTPException:
            pass

        label = "JustCall"
        if label_suffix:
            label = f"JustCall (****{label_suffix})"

        profiles.append(ProfileInfo(
            id=str(secret.get("id")),
            label=label,
            connected_at=secret.get("created_at"),
        ))

    return ProfilesResponse(
        profiles=profiles,
        provider_status=ProviderStatus(configured=bool(api_secrets)),
    )


@router.post("/campaigns", response_model=CampaignsResponse)
async def list_campaigns(
    request: CampaignsRequest,
    org: OrgContext = Depends(require_justcall_context),
    db: AsyncSession = Depends(get_session),
) -> CampaignsResponse:
    await require_byo_access(org, db)

    backend = get_saas_secret_backend()
    secret = await backend.get_secret_by_id(
        db=db,
        secret_id=request.profile_id,
        workspace_id=org.account_id,
        integration="justcall",
    )
    if not secret or not secret.value:
        raise HTTPException(status_code=404, detail="JustCall credentials not configured")

    creds = _parse_credentials(secret.value)
    adapter = JustCallAdapter(api_key=creds["api_key"], api_secret=creds["api_secret"])
    try:
        campaigns = await adapter.list_campaigns()
    except JustCallAuthError:
        raise HTTPException(status_code=401, detail="Authentication failed")
    except JustCallRateLimitError as exc:
        raise HTTPException(status_code=429, detail=str(exc))
    except JustCallError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    finally:
        await adapter.close()

    items = [
        CampaignInfo(
            id=str(c.get("id", "")),
            name=str(c.get("name", "")),
            status=c.get("status"),
            contact_count=c.get("contact_count"),
        )
        for c in campaigns
    ]
    return CampaignsResponse(campaigns=items)


@router.get("/numbers", response_model=NumbersResponse)
async def list_numbers(
    profile_id: str = Query(...),
    response: Response = None,
    org: OrgContext = Depends(require_justcall_context),
    db: AsyncSession = Depends(get_session),
) -> NumbersResponse:
    await require_byo_access(org, db)

    if response is not None:
        response.headers["Cache-Control"] = "no-store"

    backend = get_saas_secret_backend()
    secret = await backend.get_secret_by_id(
        db=db,
        secret_id=profile_id,
        workspace_id=org.account_id,
        integration="justcall",
    )
    if not secret or not secret.value:
        raise HTTPException(status_code=404, detail="JustCall credentials not configured")

    creds = _parse_credentials(secret.value)
    adapter = JustCallAdapter(api_key=creds["api_key"], api_secret=creds["api_secret"])
    try:
        numbers = await adapter.list_numbers()
    except JustCallAuthError:
        raise HTTPException(status_code=401, detail="Authentication failed")
    except JustCallRateLimitError as exc:
        raise HTTPException(status_code=429, detail=str(exc))
    except JustCallError as exc:
        raise HTTPException(status_code=502, detail=str(exc))
    finally:
        await adapter.close()

    items = [
        NumberInfo(
            id=str(item.get("id", "")),
            number=str(item.get("number", "")),
            label=item.get("label"),
            sms_enabled=item.get("sms_enabled"),
            whatsapp_enabled=item.get("whatsapp_enabled"),
            owner_id=item.get("owner_id"),
            owner_email=item.get("owner_email"),
            owner_name=item.get("owner_name"),
        )
        for item in numbers
    ]
    return NumbersResponse(numbers=items)


@router.post("/preview", response_model=PreviewResponse)
async def preview_job(
    request: JustCallRowsRequest,
    response: Response,
    org: OrgContext = Depends(require_justcall_context),
    db: AsyncSession = Depends(get_session),
) -> PreviewResponse:
    await require_byo_access(org, db)

    response.headers["Cache-Control"] = "no-store"

    if len(request.rows) > MAX_PREVIEW_ROWS:
        raise HTTPException(status_code=400, detail="Preview supports up to 500 rows")

    config = request.config
    if not config.profile_id:
        raise HTTPException(status_code=400, detail="profile_id is required")
    if not config.campaign_id:
        raise HTTPException(status_code=400, detail="campaign_id is required")

    backend = get_saas_secret_backend()
    secret = await backend.get_secret_by_id(
        db=db,
        secret_id=config.profile_id,
        workspace_id=org.account_id,
        integration="justcall",
    )
    if not secret or not secret.value:
        raise HTTPException(status_code=404, detail="JustCall credentials not configured")

    total_rows = len(request.rows)
    valid_rows = 0
    invalid_rows = 0
    skipped_rows = 0
    normalized_changed = 0
    sample_transforms: List[PreviewSampleTransform] = []
    errors: List[PreviewError] = []
    seen_phones: set[str] = set()

    phone_column = config.mapping.phone
    starting_row = config.starting_row

    for idx, row in enumerate(request.rows):
        row_number = _get_row_number(row, idx, starting_row)

        if _should_skip_row(row, config.campaign_id, config.writeback.model_dump()):
            skipped_rows += 1
            continue

        if not phone_column:
            invalid_rows += 1
            if len(errors) < MAX_ERRORS:
                errors.append(PreviewError(
                    row_number=row_number,
                    field="phone",
                    message="Phone mapping is required",
                ))
            continue

        raw_phone = str(row.get(phone_column, "")).strip()
        if not raw_phone:
            skipped_rows += 1
            if len(errors) < MAX_ERRORS:
                errors.append(PreviewError(
                    row_number=row_number,
                    field="phone",
                    message="Missing required phone",
                ))
            continue

        normalized, _, is_valid = normalize_phone_e164(raw_phone)
        if not is_valid:
            invalid_rows += 1
            if len(errors) < MAX_ERRORS:
                errors.append(PreviewError(
                    row_number=row_number,
                    field="phone",
                    message="Invalid phone format",
                ))
            continue

        if normalized and normalized != raw_phone:
            normalized_changed += 1
            if len(sample_transforms) < 5:
                sample_transforms.append(PreviewSampleTransform(
                    original=raw_phone,
                    normalized=normalized,
                ))

        if normalized in seen_phones:
            if len(errors) < MAX_ERRORS:
                errors.append(PreviewError(
                    row_number=row_number,
                    field="phone",
                    message="Duplicate phone in selection",
                ))
        else:
            seen_phones.add(normalized)

        valid_rows += 1

    return PreviewResponse(
        total_rows=total_rows,
        valid_rows=valid_rows,
        invalid_rows=invalid_rows,
        skipped_rows=skipped_rows,
        normalized_changed=normalized_changed,
        sample_transforms=sample_transforms,
        errors=errors,
    )


# =============================================================================
# Credential Management Endpoints
# =============================================================================


class CredentialsRequest(BaseModel):
    api_key: str = Field(..., description="JustCall API key")
    api_secret: str = Field(..., description="JustCall API secret")


class CredentialsResponse(BaseModel):
    ok: bool = True
    provider: str = "justcall"
    status: str = "connected"
    key_suffix: Optional[str] = None
    connected_at: Optional[str] = None


class CredentialsDeleteResponse(BaseModel):
    ok: bool = True
    message: str = "JustCall disconnected"


@router.post("/credentials", response_model=CredentialsResponse)
async def save_credentials(
    request: CredentialsRequest,
    org: OrgContext = Depends(require_justcall_context),
    db: AsyncSession = Depends(get_session),
) -> CredentialsResponse:
    """
    Save JustCall API credentials (api_key + api_secret).

    Stores the credentials as a combined JSON object encrypted with KMS.
    Validates credentials by calling JustCall campaigns endpoint.
    """
    await require_byo_access(org, db)

    api_key = request.api_key.strip()
    api_secret = request.api_secret.strip()

    if not api_key or not api_secret:
        raise HTTPException(status_code=400, detail="API key and secret are required")

    # Validate credentials by making a test API call
    adapter = JustCallAdapter(api_key=api_key, api_secret=api_secret)
    try:
        await adapter.list_campaigns()
    except JustCallAuthError:
        raise HTTPException(status_code=401, detail="Invalid JustCall credentials")
    except JustCallRateLimitError as exc:
        raise HTTPException(status_code=429, detail=str(exc))
    except JustCallError as exc:
        raise HTTPException(status_code=502, detail=f"JustCall API error: {exc}")
    finally:
        await adapter.close()

    # Store combined JSON credential
    combined_value = json.dumps({"api_key": api_key, "api_secret": api_secret})
    backend = get_saas_secret_backend()

    from datetime import datetime, timezone

    record = await backend.set_secret(
        db=db,
        integration="justcall",
        key="api_key",
        value=combined_value,
        workspace_id=org.account_id,
        created_by=org.email,
        validated_at=datetime.now(timezone.utc),
    )
    await db.commit()

    logger.info(f"[JUSTCALL] Saved credentials for workspace {org.account_id}")

    return CredentialsResponse(
        ok=True,
        provider="justcall",
        status="connected",
        key_suffix=api_key[-4:] if len(api_key) >= 4 else None,
        connected_at=record.created_at.isoformat() if record.created_at else None,
    )


@router.delete("/credentials", response_model=CredentialsDeleteResponse)
async def delete_credentials(
    org: OrgContext = Depends(require_justcall_context),
    db: AsyncSession = Depends(get_session),
) -> CredentialsDeleteResponse:
    """Remove JustCall credentials from the Provider Locker."""
    await require_byo_access(org, db)

    backend = get_saas_secret_backend()
    deleted = await backend.delete_secret(
        db=db,
        integration="justcall",
        key="api_key",
        workspace_id=org.account_id,
    )

    if not deleted:
        raise HTTPException(status_code=404, detail="No JustCall credentials found")

    await db.commit()
    logger.info(f"[JUSTCALL] Removed credentials for workspace {org.account_id}")

    return CredentialsDeleteResponse(ok=True, message="JustCall disconnected")
