"""Scoring and matrix generation for human service coverage."""

from __future__ import annotations

from collections import Counter
from typing import Iterable, Sequence

from .catalog import HUMAN_PROFILES, CATEGORY_KEYWORDS, categories_for_text
from .models import (
    EvidenceKind,
    HumanProfile,
    MatrixCell,
    MaturityLevel,
    NeedCategory,
    PlatformDefinition,
    PlatformHumanReport,
    PlatformScan,
    Recommendation,
    OrderType,
    clamp_score,
    maturity_from_score,
    merge_unique,
    score_label,
)


EVIDENCE_WEIGHTS: dict[EvidenceKind, int] = {
    EvidenceKind.README: 5,
    EvidenceKind.PACKAGE_SCRIPT: 7,
    EvidenceKind.ROUTE: 8,
    EvidenceKind.OPENAPI: 12,
    EvidenceKind.TEST: 10,
    EvidenceKind.CONFIG: 5,
    EvidenceKind.DOC: 5,
    EvidenceKind.WORKER: 8,
    EvidenceKind.STORAGE: 8,
    EvidenceKind.MCP_TOOL: 12,
    EvidenceKind.UI_SURFACE: 11,
    EvidenceKind.SECURITY: 12,
    EvidenceKind.BUSINESS_RULE: 10,
    EvidenceKind.OBSERVABILITY: 11,
    EvidenceKind.UNKNOWN: 2,
}


PROFILE_SIGNAL_BONUS: dict[NeedCategory, tuple[EvidenceKind, ...]] = {
    NeedCategory.ADMINISTRATION: (EvidenceKind.MCP_TOOL, EvidenceKind.UI_SURFACE, EvidenceKind.ROUTE),
    NeedCategory.SUPPORT: (EvidenceKind.OBSERVABILITY, EvidenceKind.ROUTE, EvidenceKind.DOC),
    NeedCategory.FINANCE: (EvidenceKind.BUSINESS_RULE, EvidenceKind.ROUTE, EvidenceKind.OPENAPI),
    NeedCategory.LEGAL: (EvidenceKind.DOC, EvidenceKind.SECURITY, EvidenceKind.OBSERVABILITY),
    NeedCategory.SECURITY: (EvidenceKind.SECURITY, EvidenceKind.OPENAPI, EvidenceKind.TEST),
    NeedCategory.OPERATIONS: (EvidenceKind.OBSERVABILITY, EvidenceKind.PACKAGE_SCRIPT, EvidenceKind.WORKER),
    NeedCategory.STRATEGY: (EvidenceKind.DOC, EvidenceKind.OBSERVABILITY, EvidenceKind.MCP_TOOL),
    NeedCategory.DOCUMENTATION: (EvidenceKind.README, EvidenceKind.DOC, EvidenceKind.OPENAPI),
    NeedCategory.SELF_SERVICE: (EvidenceKind.UI_SURFACE, EvidenceKind.ROUTE, EvidenceKind.MCP_TOOL),
    NeedCategory.COMMERCIAL: (EvidenceKind.BUSINESS_RULE, EvidenceKind.OPENAPI, EvidenceKind.ROUTE),
    NeedCategory.EXPERIENCE: (EvidenceKind.UI_SURFACE, EvidenceKind.README, EvidenceKind.MCP_TOOL),
    NeedCategory.GOVERNANCE: (EvidenceKind.OBSERVABILITY, EvidenceKind.SECURITY, EvidenceKind.OPENAPI),
    NeedCategory.INTEGRATION: (EvidenceKind.MCP_TOOL, EvidenceKind.WORKER, EvidenceKind.SECURITY),
    NeedCategory.OBSERVABILITY: (EvidenceKind.OBSERVABILITY, EvidenceKind.TEST, EvidenceKind.ROUTE),
}


def evidence_counter(scan: PlatformScan) -> Counter[EvidenceKind]:
    counter: Counter[EvidenceKind] = Counter()
    for evidence in scan.evidence:
        counter[evidence.kind] += 1
    return counter


def category_text_score(scan: PlatformScan, categories: Iterable[NeedCategory]) -> int:
    text = " ".join(
        [
            scan.platform.title,
            scan.platform.mission,
            scan.readme_excerpt,
            " ".join(evidence.summary for evidence in scan.evidence[:120]),
        ]
    ).lower()
    score = 0
    for category in categories:
        keywords = CATEGORY_KEYWORDS.get(category, ())
        hits = sum(1 for keyword in keywords if keyword.lower() in text)
        score += min(14, hits * 3)
    return min(32, score)


def base_repository_score(scan: PlatformScan) -> int:
    if not scan.exists:
        return 0
    score = 8
    if scan.git_present:
        score += 7
    if scan.readme_excerpt:
        score += 8
    if scan.code_lines > 0:
        score += 8
    if scan.code_lines >= 1000:
        score += 4
    if scan.code_lines >= 5000:
        score += 4
    if scan.has_tests:
        score += 8
    if scan.has_openapi:
        score += 8
    if scan.has_worker:
        score += 4
    if scan.scripts:
        score += min(7, len(scan.scripts))
    return min(64, score)


def profile_alignment_score(scan: PlatformScan, profile: HumanProfile) -> int:
    score = 0
    counter = evidence_counter(scan)
    for category in profile.priority_needs:
        if category in scan.platform.primary_categories:
            score += 8
        for kind in PROFILE_SIGNAL_BONUS.get(category, ()):
            score += min(9, counter[kind] * 2)
    if profile.profile_id in scan.platform.expected_profiles:
        score += 14
    categories_from_text = set(categories_for_text(scan.readme_excerpt))
    score += len(categories_from_text.intersection(profile.priority_needs)) * 4
    return min(44, score)


def penalty_score(scan: PlatformScan, profile: HumanProfile) -> int:
    penalty = 0
    warning_text = " ".join(scan.warnings).lower()
    if "sem .git" in warning_text:
        penalty += 8
    if "nenhuma linha de codigo" in warning_text:
        penalty += 20
    if "testes nao encontrados" in warning_text and NeedCategory.OPERATIONS in profile.priority_needs:
        penalty += 6
    if "openapi" in warning_text and NeedCategory.INTEGRATION in profile.priority_needs:
        penalty += 6
    if scan.platform.known_blockers:
        penalty += min(14, 5 * len(scan.platform.known_blockers))
    return penalty


def build_strengths(scan: PlatformScan, profile: HumanProfile, score: int) -> tuple[str, ...]:
    strengths: list[str] = []
    if scan.exists:
        strengths.append("repositorio real encontrado")
    if scan.git_present:
        strengths.append("historico Git local disponivel")
    if scan.readme_excerpt:
        strengths.append("README tecnico fornece contexto inicial")
    if scan.has_tests:
        strengths.append("testes foram detectados")
    if scan.has_openapi:
        strengths.append("contrato OpenAPI foi detectado")
    if scan.has_worker:
        strengths.append("sinais de Worker/Cloudflare foram detectados")
    if profile.profile_id in scan.platform.expected_profiles:
        strengths.append(f"plataforma declarada como relevante para {profile.name}")
    for category in profile.priority_needs:
        if category in scan.platform.primary_categories:
            strengths.append(f"categoria {category.value} e parte do papel principal da plataforma")
    if score >= 75:
        strengths.append("pontuacao indica atendimento humano forte ou pronto")
    return merge_unique(strengths)[:8]


def build_gaps(scan: PlatformScan, profile: HumanProfile, score: int) -> tuple[str, ...]:
    gaps: list[str] = []
    if not scan.exists:
        gaps.append("repositorio real nao existe no espelho local")
    if scan.exists and not scan.git_present:
        gaps.append("repositorio precisa de Git inicializado e remoto configurado")
    if scan.exists and not scan.readme_excerpt:
        gaps.append("falta README tecnico para leitura humana")
    if scan.exists and not scan.has_tests:
        gaps.append("faltam testes detectaveis para provar comportamento")
    if scan.exists and not scan.has_openapi and NeedCategory.INTEGRATION in profile.priority_needs:
        gaps.append("falta contrato OpenAPI ou equivalente para integracao auditavel")
    if scan.platform.known_blockers:
        gaps.extend(scan.platform.known_blockers)
    missing_categories = [category.value for category in profile.priority_needs if category not in scan.platform.primary_categories]
    if score < 60 and missing_categories:
        gaps.append("categorias humanas secundarias precisam de explicacao: " + ", ".join(missing_categories[:4]))
    if score < 40:
        gaps.append("atendimento humano ainda aparece mais planejado do que operacional")
    return merge_unique(gaps)[:8]


def evidence_refs(scan: PlatformScan, profile: HumanProfile) -> tuple[str, ...]:
    desired_kinds: set[EvidenceKind] = set()
    for category in profile.priority_needs:
        desired_kinds.update(PROFILE_SIGNAL_BONUS.get(category, ()))
    refs: list[str] = []
    for evidence in scan.evidence:
        if evidence.kind in desired_kinds or evidence.is_strong():
            refs.append(evidence.reference)
        if len(refs) >= 8:
            break
    return merge_unique(refs)


def explain_cell(scan: PlatformScan, profile: HumanProfile, score: int, maturity: MaturityLevel) -> str:
    label = score_label(score)
    if not scan.exists:
        return f"{profile.name} ainda nao tem base local analisavel em {scan.platform.title}."
    if score >= 75:
        return (
            f"{scan.platform.title} atende {profile.name} em nivel {label}, "
            f"com maturidade {maturity.value} e evidencias tecnicas suficientes para leitura humana."
        )
    if score >= 50:
        return (
            f"{scan.platform.title} ja oferece sinais uteis para {profile.name}, "
            "mas precisa transformar capacidades tecnicas em telas, relatorios ou comandos mais claros."
        )
    return (
        f"{scan.platform.title} ainda parece {label} para {profile.name}; "
        "a proxima evolucao deve explicitar necessidades humanas, evidencias e criterio de pronto."
    )


def score_cell(scan: PlatformScan, profile: HumanProfile) -> MatrixCell:
    score = base_repository_score(scan)
    score += profile_alignment_score(scan, profile)
    score += category_text_score(scan, profile.priority_needs)
    score -= penalty_score(scan, profile)
    score = clamp_score(score)
    maturity = maturity_from_score(score)
    return MatrixCell(
        platform_id=scan.platform.platform_id,
        profile_id=profile.profile_id,
        score=score,
        maturity=maturity,
        explanation=explain_cell(scan, profile, score, maturity),
        strengths=build_strengths(scan, profile, score),
        gaps=build_gaps(scan, profile, score),
        evidence_refs=evidence_refs(scan, profile),
    )


def build_matrix(scans: Sequence[PlatformScan], profiles: Sequence[HumanProfile] = HUMAN_PROFILES) -> tuple[MatrixCell, ...]:
    cells: list[MatrixCell] = []
    for scan in scans:
        for profile in profiles:
            cells.append(score_cell(scan, profile))
    return tuple(cells)


def cells_for_platform(cells: Sequence[MatrixCell], platform_id: str) -> tuple[MatrixCell, ...]:
    return tuple(cell for cell in cells if cell.platform_id == platform_id)


def top_gaps(cells: Sequence[MatrixCell], limit: int = 8) -> tuple[str, ...]:
    gaps: list[str] = []
    for cell in sorted(cells, key=lambda item: item.score):
        gaps.extend(cell.gaps)
        if len(gaps) >= limit:
            break
    return merge_unique(gaps)[:limit]


def top_strengths(cells: Sequence[MatrixCell], limit: int = 8) -> tuple[str, ...]:
    strengths: list[str] = []
    for cell in sorted(cells, key=lambda item: item.score, reverse=True):
        strengths.extend(cell.strengths)
        if len(strengths) >= limit:
            break
    return merge_unique(strengths)[:limit]


def build_recommendations_for_scan(scan: PlatformScan, cells: Sequence[MatrixCell]) -> tuple[Recommendation, ...]:
    recommendations: list[Recommendation] = []
    platform_id = scan.platform.platform_id
    low_cells = [cell for cell in cells if cell.score < 60]
    if not scan.exists:
        recommendations.append(
            Recommendation(
                recommendation_id=f"{platform_id}-criar-repositorio-real",
                platform_id=platform_id,
                title="Criar e inicializar repositorio real da plataforma",
                reason="A plataforma nao possui base local analisavel.",
                expected_impact="Permitir execucao tecnica, versionamento e sincronizacao.",
                categories=(NeedCategory.GOVERNANCE, NeedCategory.OPERATIONS),
                priority=100,
                suggested_order_type=OrderType.EXECUTIVE,
                affected_paths=(scan.repo_path,),
                validation_steps=("git status", "git remote -v", "linha de base criada"),
            )
        )
    if scan.exists and not scan.git_present:
        recommendations.append(
            Recommendation(
                recommendation_id=f"{platform_id}-inicializar-git",
                platform_id=platform_id,
                title="Inicializar Git e configurar origin correto",
                reason="O repositorio existe sem .git, impedindo rastreabilidade e sincronizacao.",
                expected_impact="Fechar base operacional minima para commits, push e hash final.",
                categories=(NeedCategory.GOVERNANCE, NeedCategory.OPERATIONS),
                priority=95,
                suggested_order_type=OrderType.EXECUTIVE,
                affected_paths=(scan.repo_path,),
                validation_steps=("git status --short --branch", "git remote -v"),
            )
        )
    if scan.exists and not scan.readme_excerpt:
        recommendations.append(
            Recommendation(
                recommendation_id=f"{platform_id}-readme-humano",
                platform_id=platform_id,
                title="Criar README tecnico e humano da plataforma",
                reason="Sem README, o estado tecnico nao vira compreensao humana inicial.",
                expected_impact="Dar missao, escopo, comandos e criterios de validacao.",
                categories=(NeedCategory.DOCUMENTATION, NeedCategory.EXPERIENCE),
                priority=90,
                suggested_order_type=OrderType.EXECUTIVE,
                affected_paths=(f"{scan.repo_path}/README.md",),
                validation_steps=("README revisado", "links e comandos existentes"),
            )
        )
    if scan.exists and not scan.has_tests:
        recommendations.append(
            Recommendation(
                recommendation_id=f"{platform_id}-testes-canonicos",
                platform_id=platform_id,
                title="Criar testes canonicos de leitura humana",
                reason="A varredura nao encontrou testes suficientes para validar comportamento.",
                expected_impact="Aumentar confianca de suporte, operacao e auditoria.",
                categories=(NeedCategory.OPERATIONS, NeedCategory.OBSERVABILITY),
                priority=75,
                suggested_order_type=OrderType.EXECUTIVE,
                affected_paths=(f"{scan.repo_path}/tests",),
                validation_steps=("suite local executada", "relatorio de testes registrado"),
            )
        )
    if low_cells:
        weakest = sorted(low_cells, key=lambda cell: cell.score)[:4]
        profile_ids = ", ".join(cell.profile_id for cell in weakest)
        recommendations.append(
            Recommendation(
                recommendation_id=f"{platform_id}-matriz-perfis-fracos",
                platform_id=platform_id,
                title="Fechar lacunas da matriz humana por perfil",
                reason=f"Perfis com baixo atendimento detectados: {profile_ids}.",
                expected_impact="Transformar capacidade tecnica em telas, relatorios e mensagens de acao.",
                categories=(NeedCategory.EXPERIENCE, NeedCategory.GOVERNANCE, NeedCategory.SUPPORT),
                priority=70,
                suggested_order_type=OrderType.MANAGERIAL,
                affected_paths=(scan.repo_path,),
                validation_steps=("matriz regenerada", "lacunas reclassificadas", "OS de continuidade criada"),
            )
        )
    if scan.platform.known_blockers:
        recommendations.append(
            Recommendation(
                recommendation_id=f"{platform_id}-bloqueios-conhecidos",
                platform_id=platform_id,
                title="Resolver ou formalizar bloqueios conhecidos",
                reason="A plataforma possui bloqueios de maturidade ja mapeados.",
                expected_impact="Reduzir contradicao entre readiness tecnico e utilidade humana.",
                categories=(NeedCategory.GOVERNANCE, NeedCategory.OBSERVABILITY),
                priority=85,
                suggested_order_type=OrderType.MANAGERIAL,
                affected_paths=(scan.repo_path,),
                validation_steps=("bloqueios documentados", "status reavaliado", "evidencia anexada"),
            )
        )
    recommendations.sort(key=lambda item: (-item.priority, item.title))
    return tuple(recommendations)


def build_platform_report(scan: PlatformScan, all_cells: Sequence[MatrixCell]) -> PlatformHumanReport:
    cells = cells_for_platform(all_cells, scan.platform.platform_id)
    recommendations = build_recommendations_for_scan(scan, cells)
    strengths = top_strengths(cells)
    gaps = top_gaps(cells)
    if scan.exists:
        summary = (
            f"{scan.platform.title} foi analisada com {scan.code_lines} linhas de codigo e "
            f"{len(scan.evidence)} evidencias locais. Score medio humano: "
            f"{round(sum(cell.score for cell in cells) / len(cells)) if cells else 0}."
        )
    else:
        summary = f"{scan.platform.title} nao possui repositorio local analisavel."
    current_state = tuple(strengths) or ("estado inicial sem evidencias fortes",)
    future_state = tuple(
        [
            "telas e relatorios devem responder quem e atendido, como e atendido e qual proxima acao",
            "evidencias devem ser exportaveis para GPT, painel e central de ordens",
            "cada lacuna humana deve gerar OS executavel com validacao clara",
        ]
    )
    missing = tuple(gaps) or ("nenhuma lacuna principal detectada pela matriz atual",)
    return PlatformHumanReport(
        platform=scan.platform,
        scan=scan,
        cells=cells,
        recommendations=recommendations,
        summary=summary,
        current_state=current_state,
        future_state=future_state,
        missing_for_humans=missing,
    )


def build_platform_reports(scans: Sequence[PlatformScan], cells: Sequence[MatrixCell]) -> tuple[PlatformHumanReport, ...]:
    return tuple(build_platform_report(scan, cells) for scan in scans)


def build_global_recommendations(reports: Sequence[PlatformHumanReport]) -> tuple[Recommendation, ...]:
    recommendations: list[Recommendation] = []
    for report in reports:
        recommendations.extend(report.recommendations[:3])
    average_by_platform = sorted(reports, key=lambda report: report.average_score)
    for report in average_by_platform[:5]:
        recommendations.append(
            Recommendation(
                recommendation_id=f"global-elevar-{report.platform.platform_id}",
                platform_id=report.platform.platform_id,
                title=f"Elevar maturidade humana de {report.platform.title}",
                reason=f"Score medio atual {report.average_score}; lacunas principais exigem continuidade.",
                expected_impact="Aumentar clareza para administradores, suporte, clientes e planejamento.",
                categories=report.platform.primary_categories,
                priority=65 + max(0, 60 - report.average_score),
                suggested_order_type=OrderType.MANAGERIAL,
                affected_paths=(report.scan.repo_path,),
                validation_steps=("relatorio regenerado", "score comparado", "pendencias atualizadas"),
            )
        )
    recommendations.sort(key=lambda item: (-item.priority, item.platform_id, item.title))
    return tuple(recommendations)


def matrix_table(cells: Sequence[MatrixCell], profiles: Sequence[HumanProfile] = HUMAN_PROFILES) -> list[list[str]]:
    platform_ids = sorted({cell.platform_id for cell in cells})
    by_pair = {(cell.platform_id, cell.profile_id): cell for cell in cells}
    header = ["platform"] + [profile.profile_id for profile in profiles]
    rows = [header]
    for platform_id in platform_ids:
        row = [platform_id]
        for profile in profiles:
            cell = by_pair.get((platform_id, profile.profile_id))
            row.append(str(cell.score if cell else 0))
        rows.append(row)
    return rows

