import re

from sqlalchemy import func
from sqlalchemy.orm import Session

from app.core.exceptions import ValidationError
from app.models.insurance_company import InsuranceCompany
from app.models.policy import Policy
from app.parsers.registry import PARSER_REGISTRY
from app.repositories.insurance_company_repository import InsuranceCompanyRepository
from app.services.scan_keyword_service import ScanKeywordService

_CODE_RE = re.compile(r"^[a-z][a-z0-9_]*$")


def normalize_company_code(code: str) -> str:
    return code.strip().lower().replace("-", "_").replace(" ", "_")


class InsuranceCompanyService:
    def __init__(self, db: Session):
        self.db = db
        self.repo = InsuranceCompanyRepository(db)

    def list_all(self) -> list[dict]:
        counts = dict(
            self.db.query(Policy.insurance_company_id, func.count(Policy.id))
            .group_by(Policy.insurance_company_id)
            .all()
        )
        rows = self.repo.list_all()
        return [
            {
                "id": row.id,
                "name": row.name,
                "code": row.code,
                "parser_key": row.parser_key,
                "is_active": row.is_active,
                "policy_count": counts.get(row.id, 0),
            }
            for row in rows
        ]

    def create(self, *, name: str, code: str, parser_key: str | None, is_active: bool) -> dict:
        clean_name = name.strip()
        clean_code = normalize_company_code(code)
        self._validate_code(clean_code)
        parser = self._resolve_parser_key(parser_key, clean_code)

        if self.repo.get_by_code(clean_code):
            raise ValidationError(f"Insurance company code '{clean_code}' already exists")
        if self.repo.get_by_name_exact(clean_name):
            raise ValidationError(f"Insurance company name '{clean_name}' already exists")

        row = InsuranceCompany(
            name=clean_name,
            code=clean_code,
            parser_key=parser,
            is_active=is_active,
        )
        self.db.add(row)
        self.db.flush()
        ScanKeywordService(self.db).sync_primary_for_company(row)
        self.db.commit()
        self.db.refresh(row)
        return self._to_item(row, 0)

    def update(self, company_id: int, **fields) -> dict:
        row = self.repo.get_by_id(company_id)
        if not row:
            raise ValidationError("Insurance company not found")

        if fields.get("name") is not None:
            clean_name = fields["name"].strip()
            existing = self.repo.get_by_name_exact(clean_name)
            if existing and existing.id != row.id:
                raise ValidationError(f"Insurance company name '{clean_name}' already exists")
            row.name = clean_name
            ScanKeywordService(self.db).sync_primary_for_company(row)

        if fields.get("code") is not None:
            clean_code = normalize_company_code(fields["code"])
            self._validate_code(clean_code)
            existing = self.repo.get_by_code(clean_code)
            if existing and existing.id != row.id:
                raise ValidationError(f"Insurance company code '{clean_code}' already exists")
            row.code = clean_code

        if "parser_key" in fields:
            row.parser_key = self._resolve_parser_key(fields["parser_key"], row.code)

        if fields.get("is_active") is not None:
            row.is_active = fields["is_active"]

        self.db.commit()
        self.db.refresh(row)
        policy_count = (
            self.db.query(func.count(Policy.id)).filter(Policy.insurance_company_id == row.id).scalar() or 0
        )
        return self._to_item(row, policy_count)

    def delete(self, company_id: int) -> None:
        row = self.repo.get_by_id(company_id)
        if not row:
            raise ValidationError("Insurance company not found")

        policy_count = (
            self.db.query(func.count(Policy.id)).filter(Policy.insurance_company_id == row.id).scalar() or 0
        )
        if policy_count:
            raise ValidationError("Cannot delete an insurer that has policies — deactivate it instead")

        self.db.delete(row)
        self.db.commit()

    def _validate_code(self, code: str) -> None:
        if not _CODE_RE.match(code):
            raise ValidationError("Company code must use lowercase letters, numbers, and underscores only")

    def _resolve_parser_key(self, parser_key: str | None, company_code: str) -> str:
        key = (parser_key or company_code or "generic").strip().lower()
        if key not in PARSER_REGISTRY:
            allowed = ", ".join(sorted(PARSER_REGISTRY.keys()))
            raise ValidationError(f"Unknown parser key '{key}'. Allowed: {allowed}")
        return key

    @staticmethod
    def _to_item(row: InsuranceCompany, policy_count: int) -> dict:
        return {
            "id": row.id,
            "name": row.name,
            "code": row.code,
            "parser_key": row.parser_key,
            "is_active": row.is_active,
            "policy_count": policy_count,
        }

    def list_parser_keys(self) -> list[str]:
        return sorted(PARSER_REGISTRY.keys())
