from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from typing import Any
from uuid import uuid4

from truthound.types import ResultFormat, Severity
from truthound.validators.base import ValidationIssue


@dataclass(frozen=True)
class ExecutionIssue:
    check_name: str
    message: str
    exception_type: str | None = None
    failure_category: str | None = None
    retry_count: int = 0

    def to_dict(self) -> dict[str, Any]:
        return {
            'check_name': self.check_name,
            'message': self.message,
            'exception_type': self.exception_type,
            'failure_category': self.failure_category,
            'retry_count': self.retry_count,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> 'ExecutionIssue':
        return cls(
            check_name=str(data.get('check_name', '')),
            message=str(data.get('message', '')),
            exception_type=data.get('exception_type'),
            failure_category=data.get('failure_category'),
            retry_count=int(data.get('retry_count', 0)),
        )


@dataclass(frozen=True)
class CheckResult:
    name: str
    category: str = 'general'
    success: bool = True
    issue_count: int = 0
    issues: tuple[ValidationIssue, ...] = field(default_factory=tuple)
    metadata: dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> dict[str, Any]:
        return {
            'name': self.name,
            'category': self.category,
            'success': self.success,
            'issue_count': self.issue_count,
            'issues': [issue.to_dict() for issue in self.issues],
            'metadata': self.metadata,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> 'CheckResult':
        issues = tuple(_validation_issue_from_dict(issue) for issue in data.get('issues', []))
        return cls(
            name=str(data.get('name', '')),
            category=str(data.get('category', 'general')),
            success=bool(data.get('success', False)),
            issue_count=int(data.get('issue_count', len(issues))),
            issues=issues,
            metadata=dict(data.get('metadata', {})),
        )


@dataclass(frozen=True)
class ValidationRunResult:
    suite_name: str
    source: str
    row_count: int
    column_count: int
    run_id: str = field(default_factory=lambda: f"run_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid4().hex[:8]}")
    run_time: datetime = field(default_factory=datetime.now)
    result_format: ResultFormat = ResultFormat.SUMMARY
    execution_mode: str = 'sequential'
    checks: tuple[CheckResult, ...] = field(default_factory=tuple)
    issues: tuple[ValidationIssue, ...] = field(default_factory=tuple)
    execution_issues: tuple[ExecutionIssue, ...] = field(default_factory=tuple)
    metadata: dict[str, Any] = field(default_factory=dict)

    @classmethod
    def from_suite(
        cls,
        *,
        suite: Any,
        issues: list[ValidationIssue],
        source: str,
        row_count: int,
        column_count: int,
        execution_mode: str,
        execution_issues: list[ExecutionIssue] | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> 'ValidationRunResult':
        by_check: dict[str, list[ValidationIssue]] = {}
        for issue in issues:
            check_name = issue.validator_name or issue.issue_type
            by_check.setdefault(check_name, []).append(issue)

        checks: list[CheckResult] = []
        known_checks: set[str] = set()
        for spec in suite.checks:
            spec_issues = tuple(by_check.get(spec.name, []))
            checks.append(
                CheckResult(
                    name=spec.name,
                    category=spec.category,
                    success=len(spec_issues) == 0,
                    issue_count=len(spec_issues),
                    issues=spec_issues,
                    metadata=dict(spec.metadata),
                )
            )
            known_checks.add(spec.name)

        for name, extra_issues in by_check.items():
            if name in known_checks:
                continue
            checks.append(
                CheckResult(
                    name=name,
                    success=len(extra_issues) == 0,
                    issue_count=len(extra_issues),
                    issues=tuple(extra_issues),
                )
            )

        return cls(
            run_id=f"run_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid4().hex[:8]}",
            run_time=datetime.now(),
            suite_name=suite.name,
            source=source,
            row_count=row_count,
            column_count=column_count,
            result_format=suite.evidence_policy.result_format.format,
            execution_mode=execution_mode,
            checks=tuple(checks),
            issues=tuple(issues),
            execution_issues=tuple(execution_issues or ()),
            metadata=dict(metadata or {}),
        )

    @property
    def has_issues(self) -> bool:
        return bool(self.issues)

    @property
    def has_failures(self) -> bool:
        return self.has_issues or bool(self.execution_issues)

    def filter_by_severity(self, min_severity: Severity) -> 'ValidationRunResult':
        filtered_issues = tuple(
            issue for issue in self.issues if issue.severity >= min_severity
        )
        filtered_checks = tuple(
            CheckResult(
                name=check.name,
                category=check.category,
                success=not any(issue.severity >= min_severity for issue in check.issues),
                issue_count=sum(1 for issue in check.issues if issue.severity >= min_severity),
                issues=tuple(
                    issue for issue in check.issues if issue.severity >= min_severity
                ),
                metadata=dict(check.metadata),
            )
            for check in self.checks
        )
        return ValidationRunResult(
            run_id=self.run_id,
            run_time=self.run_time,
            suite_name=self.suite_name,
            source=self.source,
            row_count=self.row_count,
            column_count=self.column_count,
            result_format=self.result_format,
            execution_mode=self.execution_mode,
            checks=filtered_checks,
            issues=filtered_issues,
            execution_issues=self.execution_issues,
            metadata=dict(self.metadata),
        )

    def to_legacy_report(self):
        from truthound.report import Report

        report = Report(
            issues=list(self.issues),
            source=self.source,
            row_count=self.row_count,
            column_count=self.column_count,
            result_format=self.result_format,
        )
        report.validation_run = self
        return report

    def to_dict(self) -> dict[str, Any]:
        return {
            'run_id': self.run_id,
            'run_time': self.run_time.isoformat(),
            'suite_name': self.suite_name,
            'source': self.source,
            'row_count': self.row_count,
            'column_count': self.column_count,
            'result_format': self.result_format.value,
            'execution_mode': self.execution_mode,
            'checks': [check.to_dict() for check in self.checks],
            'issues': [issue.to_dict() for issue in self.issues],
            'execution_issues': [issue.to_dict() for issue in self.execution_issues],
            'metadata': self.metadata,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> 'ValidationRunResult':
        return cls(
            run_id=str(data.get('run_id', '')),
            run_time=datetime.fromisoformat(data['run_time']) if data.get('run_time') else datetime.now(),
            suite_name=str(data.get('suite_name', '')),
            source=str(data.get('source', '')),
            row_count=int(data.get('row_count', 0)),
            column_count=int(data.get('column_count', 0)),
            result_format=ResultFormat(str(data.get('result_format', ResultFormat.SUMMARY.value))),
            execution_mode=str(data.get('execution_mode', 'sequential')),
            checks=tuple(CheckResult.from_dict(check) for check in data.get('checks', [])),
            issues=tuple(_validation_issue_from_dict(issue) for issue in data.get('issues', [])),
            execution_issues=tuple(
                ExecutionIssue.from_dict(issue) for issue in data.get('execution_issues', [])
            ),
            metadata=dict(data.get('metadata', {})),
        )


def _validation_issue_from_dict(data: dict[str, Any]) -> ValidationIssue:
    severity = data.get('severity', Severity.LOW.value)
    return ValidationIssue(
        column=str(data.get('column', '_table_')),
        issue_type=str(data.get('issue_type', 'unknown_issue')),
        count=int(data.get('count', 0)),
        severity=Severity(str(severity).lower()),
        details=data.get('details'),
        expected=data.get('expected'),
        actual=data.get('actual'),
        sample_values=list(data.get('sample_values', [])) or None,
        validator_name=data.get('validator_name'),
        success=bool(data.get('success', False)),
    )
