"""
File Integrity Manager - Main service for file integrity verification.

Provides smart verification with embedded statistics and sparse failure logging.
"""

from datetime import datetime, UTC
from pathlib import Path
from typing import Optional, Tuple, List
from loguru import logger

from .base_verifier import BaseFileVerifier
from ...database.models.file_integrity import (
    FileIntegrityRecord,
    FileVerificationFailure,
)

# Import session context conditionally (requires Flask)
try:
    from ...database.session_context import get_user_db_session

    _has_session_context = True
except ImportError:
    _has_session_context = False
    # Provide stub for type checking
    get_user_db_session = None  # type: ignore


class FileIntegrityManager:
    """
    Central service for file integrity verification.

    Features:
    - Smart verification (only verify if file modified)
    - Embedded statistics (low overhead)
    - Sparse failure logging (audit trail)
    - Multi-verifier support (different file types)
    - Automatic cleanup of old failure records
    """

    # Configuration for automatic cleanup
    MAX_FAILURES_PER_FILE = 100  # Keep at most this many failures per file
    MAX_TOTAL_FAILURES = 10000  # Global limit across all files

    def __init__(self, username: str, password: Optional[str] = None):
        """
        Initialize file integrity manager.

        Args:
            username: Username for database access
            password: Optional password for encrypted database

        Raises:
            ImportError: If Flask/session_context not available
        """
        if not _has_session_context:
            raise ImportError(
                "FileIntegrityManager requires Flask and database session context. "
                "Install Flask to use this feature."
            )

        self.username = username
        self.password = password
        self.verifiers: List[BaseFileVerifier] = []

        # Run startup cleanup to remove old failures
        try:
            deleted = self.cleanup_all_old_failures()
            if deleted > 0:
                logger.info(
                    f"[FILE_INTEGRITY] Startup cleanup: removed {deleted} old failure records"
                )
        except Exception as e:
            logger.warning(f"[FILE_INTEGRITY] Startup cleanup failed: {e}")

    def _normalize_path(self, file_path: Path) -> str:
        """
        Normalize path for consistent storage and lookup.

        Resolves symlinks, makes absolute, and normalizes separators
        to ensure the same file is always represented the same way.

        Args:
            file_path: Path to normalize

        Returns:
            Normalized path string
        """
        return str(file_path.resolve())

    def register_verifier(self, verifier: BaseFileVerifier) -> None:
        """
        Register a file type verifier.

        Args:
            verifier: Verifier instance to register
        """
        self.verifiers.append(verifier)
        logger.debug(
            f"[FILE_INTEGRITY] Registered verifier for type: {verifier.get_file_type()}"
        )

    def record_file(
        self,
        file_path: Path,
        related_entity_type: Optional[str] = None,
        related_entity_id: Optional[int] = None,
    ) -> FileIntegrityRecord:
        """
        Create or update integrity record for a file.

        Args:
            file_path: Path to file to record
            related_entity_type: Optional related entity type (e.g., 'rag_index')
            related_entity_id: Optional related entity ID

        Returns:
            FileIntegrityRecord instance

        Raises:
            FileNotFoundError: If file doesn't exist
            ValueError: If no verifier handles this file type
        """
        if not file_path.exists():
            raise FileNotFoundError(f"File not found: {file_path}")

        verifier = self._get_verifier_for_file(file_path)
        if not verifier:
            raise ValueError(f"No verifier registered for file: {file_path}")

        # Calculate checksum and get file stats
        checksum = verifier.calculate_checksum(file_path)
        file_stat = file_path.stat()
        normalized_path = self._normalize_path(file_path)

        with get_user_db_session(self.username, self.password) as session:
            # Check if record exists (using normalized path)
            record = (
                session.query(FileIntegrityRecord)
                .filter_by(file_path=normalized_path)
                .first()
            )

            if record:
                # Update existing record
                record.checksum = checksum
                record.file_size = file_stat.st_size
                record.file_mtime = file_stat.st_mtime
                record.algorithm = verifier.get_algorithm()
                record.updated_at = datetime.now(UTC)
                logger.info(f"[FILE_INTEGRITY] Updated record for: {file_path}")
            else:
                # Create new record
                record = FileIntegrityRecord(
                    file_path=normalized_path,
                    file_type=verifier.get_file_type(),
                    checksum=checksum,
                    algorithm=verifier.get_algorithm(),
                    file_size=file_stat.st_size,
                    file_mtime=file_stat.st_mtime,
                    verify_on_load=True,
                    allow_modifications=verifier.allows_modifications(),
                    related_entity_type=related_entity_type,
                    related_entity_id=related_entity_id,
                    total_verifications=0,
                    consecutive_successes=0,
                    consecutive_failures=0,
                )
                session.add(record)
                logger.info(
                    f"[FILE_INTEGRITY] Created record for: {file_path} (type: {verifier.get_file_type()})"
                )

            session.commit()
            session.refresh(record)
            return record

    def verify_file(
        self, file_path: Path, force: bool = False
    ) -> Tuple[bool, Optional[str]]:
        """
        Verify file integrity with smart checking.

        Only verifies if:
        - File modification time changed since last verification, OR
        - force=True

        Args:
            file_path: Path to file to verify
            force: Force verification even if file hasn't changed

        Returns:
            Tuple of (success, reason_if_failed)
        """
        normalized_path = self._normalize_path(file_path)

        with get_user_db_session(self.username, self.password) as session:
            record = (
                session.query(FileIntegrityRecord)
                .filter_by(file_path=normalized_path)
                .first()
            )

            if not record:
                logger.warning(
                    f"[FILE_INTEGRITY] No record found for {file_path}, creating one"
                )
                # Create record if it doesn't exist
                try:
                    # Need to commit and return since we're in a different session
                    session.close()
                    self.record_file(file_path)
                    return True, None
                except Exception as e:
                    logger.exception("[FILE_INTEGRITY] Failed to create record")
                    return False, f"Failed to create integrity record: {str(e)}"

            # Check if verification needed
            if not force and not self._needs_verification(record, file_path):
                logger.debug(
                    f"[FILE_INTEGRITY] Skipping verification for {file_path} (unchanged)"
                )
                return True, None

            # Perform verification
            passed, reason = self._do_verification(record, file_path, session)

            # Update statistics
            self._update_stats(record, passed, session)

            # Log failure if needed
            if not passed:
                self._log_failure(
                    record, file_path, reason or "Unknown failure", session
                )

            session.commit()

            if passed:
                logger.info(
                    f"[FILE_INTEGRITY] Verification passed: {file_path}"
                )
            else:
                logger.error(
                    f"[FILE_INTEGRITY] Verification FAILED: {file_path} - {reason}"
                )

            return passed, reason

    def update_checksum(self, file_path: Path) -> None:
        """
        Update checksum after legitimate file modification.

        Use this when you know a file was legitimately modified
        and want to update the baseline checksum.

        Args:
            file_path: Path to file

        Raises:
            FileNotFoundError: If file doesn't exist
            ValueError: If no record exists for file
        """
        if not file_path.exists():
            raise FileNotFoundError(f"File not found: {file_path}")

        verifier = self._get_verifier_for_file(file_path)
        if not verifier:
            raise ValueError(f"No verifier registered for file: {file_path}")

        checksum = verifier.calculate_checksum(file_path)
        file_stat = file_path.stat()

        with get_user_db_session(self.username, self.password) as session:
            record = (
                session.query(FileIntegrityRecord)
                .filter_by(file_path=str(file_path))
                .first()
            )

            if not record:
                raise ValueError(f"No integrity record exists for: {file_path}")

            record.checksum = checksum
            record.file_size = file_stat.st_size
            record.file_mtime = file_stat.st_mtime
            record.updated_at = datetime.now(UTC)

            session.commit()
            logger.info(f"[FILE_INTEGRITY] Updated checksum for: {file_path}")

    def get_file_stats(self, file_path: Path) -> Optional[dict]:
        """
        Get verification statistics for a file.

        Args:
            file_path: Path to file

        Returns:
            Dictionary of stats or None if no record exists
        """
        with get_user_db_session(self.username, self.password) as session:
            record = (
                session.query(FileIntegrityRecord)
                .filter_by(file_path=str(file_path))
                .first()
            )

            if not record:
                return None

            return {
                "total_verifications": record.total_verifications,
                "last_verified_at": record.last_verified_at,
                "last_verification_passed": record.last_verification_passed,
                "consecutive_successes": record.consecutive_successes,
                "consecutive_failures": record.consecutive_failures,
                "file_type": record.file_type,
                "created_at": record.created_at,
            }

    def get_failure_history(
        self, file_path: Path, limit: int = 100
    ) -> List[FileVerificationFailure]:
        """
        Get failure history for a file.

        Args:
            file_path: Path to file
            limit: Maximum number of failures to return

        Returns:
            List of failure records
        """
        with get_user_db_session(self.username, self.password) as session:
            record = (
                session.query(FileIntegrityRecord)
                .filter_by(file_path=str(file_path))
                .first()
            )

            if not record:
                return []

            failures = (
                session.query(FileVerificationFailure)
                .filter_by(file_record_id=record.id)
                .order_by(FileVerificationFailure.verified_at.desc())
                .limit(limit)
                .all()
            )

            # Detach from session
            for f in failures:
                session.expunge(f)

            return failures

    # Internal methods

    def _get_verifier_for_file(
        self, file_path: Path
    ) -> Optional[BaseFileVerifier]:
        """Find verifier that handles this file type."""
        for verifier in self.verifiers:
            if verifier.should_verify(file_path):
                return verifier
        return None

    def _needs_verification(
        self, record: FileIntegrityRecord, file_path: Path
    ) -> bool:
        """
        Check if file needs verification.

        Only verify if file modification time changed since last verification.
        """
        if not file_path.exists():
            return True  # File missing needs verification

        if not record.last_verified_at:
            return True  # Never verified

        current_mtime = file_path.stat().st_mtime

        # Compare with stored mtime
        if record.file_mtime is None:
            return True  # No mtime stored

        # Verify if file was modified (allow small floating point differences)
        return abs(current_mtime - record.file_mtime) > 0.001

    def _do_verification(
        self, record: FileIntegrityRecord, file_path: Path, session
    ) -> Tuple[bool, Optional[str]]:
        """
        Perform actual verification.

        Returns:
            Tuple of (success, reason_if_failed)
        """
        # Check file exists
        if not file_path.exists():
            return False, "file_missing"

        # Get verifier
        verifier = self._get_verifier_for_file(file_path)
        if not verifier:
            return False, "no_verifier"

        # Calculate current checksum
        try:
            current_checksum = verifier.calculate_checksum(file_path)
        except Exception as e:
            logger.exception("[FILE_INTEGRITY] Failed to calculate checksum")
            return False, f"checksum_calculation_failed: {str(e)}"

        # Compare checksums
        if current_checksum != record.checksum:
            return False, "checksum_mismatch"

        # Update file mtime in record
        record.file_mtime = file_path.stat().st_mtime

        return True, None

    def _update_stats(
        self, record: FileIntegrityRecord, passed: bool, session
    ) -> None:
        """Update verification statistics."""
        record.total_verifications += 1
        record.last_verified_at = datetime.now(UTC)
        record.last_verification_passed = passed

        if passed:
            record.consecutive_successes += 1
            record.consecutive_failures = 0
        else:
            record.consecutive_failures += 1
            record.consecutive_successes = 0

    def _log_failure(
        self,
        record: FileIntegrityRecord,
        file_path: Path,
        reason: str,
        session,
    ) -> None:
        """Log verification failure to audit trail."""
        # Get current checksum if possible
        actual_checksum = None
        file_size = None

        if file_path.exists():
            try:
                verifier = self._get_verifier_for_file(file_path)
                if verifier:
                    actual_checksum = verifier.calculate_checksum(file_path)
                    file_size = file_path.stat().st_size
            except Exception:
                pass  # Checksum calculation failed, leave as None

        failure = FileVerificationFailure(
            file_record_id=record.id,
            expected_checksum=record.checksum,
            actual_checksum=actual_checksum,
            file_size=file_size,
            failure_reason=reason,
        )
        session.add(failure)

        logger.warning(
            f"[FILE_INTEGRITY] Logged failure for {file_path}: {reason}"
        )

        # Cleanup old failures for this file
        self._cleanup_old_failures(record, session)

        # Periodically check if global cleanup needed (every 100th file to avoid overhead)
        if record.id % 100 == 0:
            self._check_global_cleanup_needed(session)

    def _cleanup_old_failures(
        self, record: FileIntegrityRecord, session
    ) -> None:
        """
        Clean up old failure records to prevent unbounded growth.

        Keeps only the most recent MAX_FAILURES_PER_FILE failures per file.
        """
        # Count failures for this file
        failure_count = (
            session.query(FileVerificationFailure)
            .filter_by(file_record_id=record.id)
            .count()
        )

        if failure_count > self.MAX_FAILURES_PER_FILE:
            # Delete oldest failures, keeping only the most recent MAX_FAILURES_PER_FILE
            failures_to_delete = (
                session.query(FileVerificationFailure)
                .filter_by(file_record_id=record.id)
                .order_by(FileVerificationFailure.verified_at.asc())
                .limit(failure_count - self.MAX_FAILURES_PER_FILE)
                .all()
            )

            for failure in failures_to_delete:
                session.delete(failure)

            logger.info(
                f"[FILE_INTEGRITY] Cleaned up {len(failures_to_delete)} old failures for file_record {record.id}"
            )

    def _check_global_cleanup_needed(self, session) -> None:
        """
        Check if global cleanup is needed and run it if threshold exceeded.

        Only runs cleanup if failure count exceeds MAX_TOTAL_FAILURES by 20%.
        This prevents constant cleanup while allowing some buffer.
        """
        threshold = int(self.MAX_TOTAL_FAILURES * 1.2)  # 20% over limit
        total_failures = session.query(FileVerificationFailure).count()

        if total_failures > threshold:
            logger.info(
                f"[FILE_INTEGRITY] Global failure count ({total_failures}) exceeds threshold ({threshold}), "
                f"running cleanup..."
            )

            # Delete oldest failures to get under limit
            failures_to_delete_count = total_failures - self.MAX_TOTAL_FAILURES

            failures_to_delete = (
                session.query(FileVerificationFailure)
                .order_by(FileVerificationFailure.verified_at.asc())
                .limit(failures_to_delete_count)
                .all()
            )

            for failure in failures_to_delete:
                session.delete(failure)

            logger.info(
                f"[FILE_INTEGRITY] Threshold cleanup: deleted {len(failures_to_delete)} old failures"
            )

    def cleanup_all_old_failures(self) -> int:
        """
        Global cleanup of failure records across all files.

        Enforces MAX_TOTAL_FAILURES limit by removing oldest failures.

        Returns:
            Number of records deleted
        """
        with get_user_db_session(self.username, self.password) as session:
            total_failures = session.query(FileVerificationFailure).count()

            if total_failures <= self.MAX_TOTAL_FAILURES:
                return 0

            # Delete oldest failures to get under limit
            failures_to_delete_count = total_failures - self.MAX_TOTAL_FAILURES

            failures_to_delete = (
                session.query(FileVerificationFailure)
                .order_by(FileVerificationFailure.verified_at.asc())
                .limit(failures_to_delete_count)
                .all()
            )

            for failure in failures_to_delete:
                session.delete(failure)

            session.commit()

            logger.info(
                f"[FILE_INTEGRITY] Global cleanup: deleted {len(failures_to_delete)} old failures "
                f"(total was {total_failures}, now {total_failures - len(failures_to_delete)})"
            )

            return len(failures_to_delete)

    def get_total_failure_count(self) -> int:
        """
        Get total number of failure records across all files.

        Returns:
            Total count of failure records
        """
        with get_user_db_session(self.username, self.password) as session:
            return session.query(FileVerificationFailure).count()
