"""Company phone numbers manager: manual add, CSV/TXT import, activation toggle,
usage counts, delete, Clear All. Feeds %CompanyPhone% variable in campaigns."""
import csv
import io
import re
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile
from pydantic import BaseModel, field_validator
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_current_user, get_db, require_csrf
from app.core.security import log_audit
from app.models import CompanyPhoneNumber, User

router = APIRouter(prefix="/phones", tags=["phones"])

# Accepts international, US/UK/IN formats with spaces, dashes, dots, parentheses
# and optional extension (ext/x). Enforces 7-15 digits (excluding extension).
PHONE_RE = re.compile(r"^\+?\(?[\d][0-9 ()\-.]{3,30}(?:\s*(?:ext\.?|x)\s*\d{1,6})?$", re.IGNORECASE)


def normalize_phone(raw: str) -> Optional[str]:
    """Validate + lightly normalize a phone number.

    Returns the cleaned string (whitespace collapsed) when valid, else None.
    """
    v = " ".join((raw or "").split())
    if not v or not PHONE_RE.match(v):
        return None
    # split off any extension before counting digits
    base = re.split(r"(?:ext\.?\s*|x\s*)\d{1,6}$", v, flags=re.IGNORECASE)[0]
    digits = re.sub(r"\D", "", base)
    if not (7 <= len(digits) <= 15):
        return None
    return v


def digits_only(raw: str) -> str:
    """Digit-normalized form used for duplicate detection."""
    base = re.split(r"(?:ext\.?\s*|x\s*)\d{1,6}$", " ".join((raw or "").split()),
                    flags=re.IGNORECASE)[0]
    return re.sub(r"\D", "", base)


class PhoneIn(BaseModel):
    phone: str
    label: str = ""

    @field_validator("phone")
    @classmethod
    def valid_phone(cls, v):
        v = v.strip()
        cleaned = normalize_phone(v)
        if cleaned is None:
            raise ValueError(
                "Invalid phone number format. Use digits with spaces/dashes/dots/"
                "parentheses and an optional extension, e.g. +1 (555) 123-4567 or "
                "555.123.4567 ext. 22")
        return cleaned


class PhonePatch(BaseModel):
    label: Optional[str] = None
    is_active: Optional[bool] = None


def _serialize(p: CompanyPhoneNumber) -> dict:
    return {
        "id": p.id,
        "label": p.label,
        "phone": p.phone,
        "is_active": bool(p.is_active),
        "use_count": p.use_count,
        "created_at": p.created_at.isoformat() if p.created_at else None,
    }


async def _get_own(db: AsyncSession, user: User, pid: int) -> CompanyPhoneNumber:
    p = await db.get(CompanyPhoneNumber, pid)
    if not p or p.user_id != user.id:
        raise HTTPException(status_code=404, detail="Phone number not found")
    return p


@router.get("/list")
async def list_phones(active_only: bool = Query(False),
                      search: str = Query(""),
                      page: int = Query(1, ge=1),
                      page_size: int = Query(200, ge=1, le=500),
                      db: AsyncSession = Depends(get_db),
                      user: User = Depends(get_current_user)):
    q = select(CompanyPhoneNumber).where(CompanyPhoneNumber.user_id == user.id)
    if active_only:
        q = q.where(CompanyPhoneNumber.is_active == True)  # noqa: E712
    if search:
        q = q.where(CompanyPhoneNumber.phone.ilike(f"%{search}%")
                    | CompanyPhoneNumber.label.ilike(f"%{search}%"))
    total = (await db.execute(
        select(func.count()).select_from(q.subquery()))).scalar() or 0
    rows = (await db.execute(
        q.order_by(CompanyPhoneNumber.id.desc()).offset((page - 1) * page_size).limit(page_size)
    )).scalars().all()
    return {"phones": [_serialize(p) for p in rows],
            "total": total, "page": page, "page_size": page_size}


@router.post("/add")
async def add_phone(payload: PhoneIn, db: AsyncSession = Depends(get_db),
                    user: User = Depends(get_current_user),
                    _: None = Depends(require_csrf)):
    """Add one phone number manually."""
    dup = await db.execute(
        select(CompanyPhoneNumber).where(CompanyPhoneNumber.user_id == user.id,
                                         CompanyPhoneNumber.phone == payload.phone))
    row = dup.first()
    if row:
        raise HTTPException(status_code=409, detail="Phone number already exists")
    # also block digit-equivalent duplicates (e.g. '+1 555 010 0001' vs '555-010-0001')
    target_digits = digits_only(payload.phone)
    for other in await db.execute(
            select(CompanyPhoneNumber).where(CompanyPhoneNumber.user_id == user.id)):
        if digits_only(other[0].phone) == target_digits:
            raise HTTPException(status_code=409, detail="Phone number already exists")
    p = CompanyPhoneNumber(user_id=user.id, phone=payload.phone, label=payload.label.strip())
    db.add(p)
    await db.commit()
    await log_audit(db, user.id, "phone.add", "company_phone", p.id, {"label": p.label})
    return _serialize(p)


@router.post("/import")
async def import_phones(file: UploadFile = None, db: AsyncSession = Depends(get_db),
                        user: User = Depends(get_current_user),
                        _: None = Depends(require_csrf)):
    """Import phone numbers from CSV (phone[,label]) or TXT (one per line).
    Supports bulk import of 10+ numbers at once."""
    if file is None:
        raise HTTPException(status_code=400, detail="No file uploaded")
    raw = await file.read()
    if len(raw) > 2 * 1024 * 1024:
        raise HTTPException(status_code=413, detail="File exceeds 2MB")
    try:
        text = raw.decode("utf-8-sig")
    except UnicodeDecodeError:
        raise HTTPException(status_code=400, detail="File must be UTF-8 text")

    # reject JSON-style input explicitly (CSV/TXT only per spec)
    stripped = text.lstrip()
    if stripped.startswith("{") or stripped.startswith("["):
        raise HTTPException(status_code=400, detail="Only CSV or TXT files are accepted")

    entries: list[dict] = []
    invalid: list[str] = []
    if "," in "\n".join(text.splitlines()[:50]) or file.filename.lower().endswith(".csv"):
        reader = csv.reader(io.StringIO(text))
        for row in reader:
            if not row or not any(c.strip() for c in row):
                continue
            phone = (row[0] or "").strip()
            label = (row[1] or "").strip() if len(row) > 1 else ""
            if not phone or phone.lower().startswith(("phone", "number", "#")):
                continue  # header
            cleaned = normalize_phone(phone)
            if cleaned:
                entries.append({"phone": cleaned, "label": label})
            else:
                invalid.append(phone)
    else:
        for line in text.splitlines():
            phone = line.strip()
            if not phone or phone.lower().startswith(("phone", "number", "#")):
                continue
            cleaned = normalize_phone(phone)
            if cleaned:
                entries.append({"phone": cleaned, "label": ""})
            else:
                invalid.append(phone)

    if not entries:
        raise HTTPException(status_code=400,
                            detail=f"No valid phone numbers found. Invalid: {invalid[:10]}")

    existing = set((await db.execute(
        select(CompanyPhoneNumber.phone).where(CompanyPhoneNumber.user_id == user.id))).scalars())
    existing_digits = {digits_only(p) for p in existing}
    added, duplicates = 0, 0
    for e in entries:
        # duplicate check on digit-normalized form (so '+1 555 010 0001' == '555-010-0001')
        d_only = digits_only(e["phone"])
        if e["phone"] in existing or d_only in existing_digits:
            duplicates += 1
            continue
        db.add(CompanyPhoneNumber(user_id=user.id, phone=e["phone"], label=e["label"]))
        existing.add(e["phone"])
        existing_digits.add(d_only)
        added += 1
    await db.commit()
    await log_audit(db, user.id, "phone.import", "company_phone", None,
                    {"added": added, "duplicates": duplicates, "invalid": len(invalid)})
    return {"added": added, "duplicates": duplicates,
            "invalid": invalid[:20], "total_valid": len(entries)}


@router.post("/{pid}/patch")
async def patch_phone(pid: int, payload: PhonePatch,
                      db: AsyncSession = Depends(get_db),
                      user: User = Depends(get_current_user),
                      _: None = Depends(require_csrf)):
    p = await _get_own(db, user, pid)
    if payload.label is not None:
        p.label = payload.label.strip()[:150]
    if payload.is_active is not None:
        p.is_active = bool(payload.is_active)
    await db.commit()
    await log_audit(db, user.id, "phone.patch", "company_phone", pid,
                    {"is_active": p.is_active})
    return _serialize(p)


@router.post("/{pid}/delete")
async def delete_phone(pid: int, db: AsyncSession = Depends(get_db),
                       user: User = Depends(get_current_user),
                       _: None = Depends(require_csrf)):
    p = await _get_own(db, user, pid)
    phone_value = p.phone
    await db.delete(p)
    await db.commit()
    await log_audit(db, user.id, "phone.delete", "company_phone", pid, {"phone": phone_value})
    return {"deleted": pid}


@router.post("/clear-all")
async def clear_all_phones(db: AsyncSession = Depends(get_db),
                           user: User = Depends(get_current_user),
                           _: None = Depends(require_csrf)):
    rows = (await db.execute(
        select(CompanyPhoneNumber).where(CompanyPhoneNumber.user_id == user.id))).scalars().all()
    for p in rows:
        await db.delete(p)
    await db.commit()
    await log_audit(db, user.id, "phone.clear_all", "company_phone", None,
                    {"deleted": len(rows)})
    return {"deleted": len(rows)}
