"""DSS Archive Importer - Loads .dss files and restores project state"""

import json
import zipfile
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple
from dataclasses import dataclass
from .migrations import MigrationManager
from .security import (
    ZipSlipValidator,
    MemoryLimitManager,
    StreamingJsonLoader,
    ArchiveIntegrity,
)
from ..models.project import Project, ProjectMetadata
from ..models.theme import Theme, DesignToken, TokenCategory
from ..models.component import Component


@dataclass
class ImportValidationError:
    """Validation error details"""

    stage: str  # archive, manifest, schema, structure, referential
    message: str
    details: Optional[Dict[str, Any]] = None


@dataclass
class ImportAnalysis:
    """Analysis of archive before import"""

    is_valid: bool
    errors: List[ImportValidationError]
    warnings: List[str]
    schema_version: str
    project_name: str
    content_summary: Dict[str, int]  # counts: tokens, components, themes
    migration_needed: bool
    target_version: str


class ArchiveValidator:
    """Validates .dss archive integrity"""

    @staticmethod
    def validate_archive_structure(archive: zipfile.ZipFile) -> Optional[ImportValidationError]:
        """Validate basic archive structure and security (Zip Slip protection)"""
        required_files = ["manifest.json"]
        archive_files = archive.namelist()

        # Security: Check for Zip Slip vulnerability (path traversal)
        is_safe, unsafe_paths = ZipSlipValidator.validate_archive_members(archive_files)
        if not is_safe:
            return ImportValidationError(
                stage="archive",
                message=f"Archive contains unsafe paths (Zip Slip vulnerability detected): {unsafe_paths}",
                details={"unsafe_paths": unsafe_paths},
            )

        for required in required_files:
            if required not in archive_files:
                return ImportValidationError(
                    stage="archive",
                    message=f"Missing required file: {required}",
                )
        return None

    @staticmethod
    def validate_manifest(manifest: Dict[str, Any]) -> Optional[ImportValidationError]:
        """Validate manifest.json structure and integrity"""
        required_fields = ["schemaVersion", "projectName", "projectUuid"]
        for field in required_fields:
            if field not in manifest:
                return ImportValidationError(
                    stage="manifest",
                    message=f"Missing required manifest field: {field}",
                )

        # Validate version format
        version = manifest.get("schemaVersion", "")
        if not _is_valid_version(version):
            return ImportValidationError(
                stage="manifest",
                message=f"Invalid schema version format: {version}",
            )

        # Security: Verify manifest integrity if hash is present
        is_valid, error_msg = ArchiveIntegrity.verify_manifest_integrity(manifest)
        if not is_valid:
            return ImportValidationError(
                stage="manifest",
                message=error_msg or "Manifest integrity check failed",
            )

        return None

    @staticmethod
    def validate_schema_version(
        archive_version: str, current_version: str
    ) -> Optional[ImportValidationError]:
        """Check if schema version can be migrated"""
        if archive_version > current_version:
            return ImportValidationError(
                stage="schema",
                message=f"Archive schema {archive_version} is newer than app supports ({current_version}). "
                f"Please update DSS application.",
            )
        return None

    @staticmethod
    def validate_referential_integrity(
        data: Dict[str, Any], local_uuids: Optional[Dict[str, set]] = None
    ) -> List[ImportValidationError]:
        """Validate all UUID references are resolvable"""
        errors = []
        local_uuids = local_uuids or {"tokens": set(), "components": set()}

        # Build UUID map from imported data
        token_uuids = {t["uuid"] for t in data.get("tokens", {}).values() if isinstance(t, dict)}
        component_uuids = {c["uuid"] for c in data.get("components", [])}

        # Merge with local UUIDs
        all_token_uuids = token_uuids | local_uuids.get("tokens", set())
        all_component_uuids = component_uuids | local_uuids.get("components", set())

        # Check component dependencies
        for comp in data.get("components", []):
            for dep_uuid in comp.get("dependencies", []):
                if dep_uuid not in all_component_uuids:
                    errors.append(
                        ImportValidationError(
                            stage="referential",
                            message=f"Component {comp['name']} references unknown component: {dep_uuid}",
                        )
                    )

        return errors


class DSSArchiveImporter:
    """Imports .dss archives into DSS"""

    def __init__(self, archive_path: Path):
        self.archive_path = Path(archive_path)
        self.archive: Optional[zipfile.ZipFile] = None
        self.manifest: Optional[Dict[str, Any]] = None
        self.data: Optional[Dict[str, Any]] = None

    def analyze(self) -> ImportAnalysis:
        """Analyze archive without importing"""
        errors = []
        warnings = []

        try:
            # Open and validate archive
            with zipfile.ZipFile(self.archive_path, "r") as archive:
                # Check structure
                struct_err = ArchiveValidator.validate_archive_structure(archive)
                if struct_err:
                    errors.append(struct_err)
                    return ImportAnalysis(
                        is_valid=False,
                        errors=errors,
                        warnings=warnings,
                        schema_version="unknown",
                        project_name="unknown",
                        content_summary={},
                        migration_needed=False,
                        target_version="",
                    )

                # Read manifest
                manifest_json = archive.read("manifest.json").decode("utf-8")
                self.manifest = json.loads(manifest_json)

                # Validate manifest
                manifest_err = ArchiveValidator.validate_manifest(self.manifest)
                if manifest_err:
                    errors.append(manifest_err)

                # Check schema version
                schema_version = self.manifest.get("schemaVersion", "1.0.0")
                current_version = MigrationManager.get_latest_version()
                version_err = ArchiveValidator.validate_schema_version(schema_version, current_version)
                if version_err:
                    errors.append(version_err)

                migration_needed = schema_version != current_version

                # Load data with memory limits
                memory_mgr = MemoryLimitManager()
                data = {}

                for json_file in ["tokens.json", "components.json", "themes.json", "config.json"]:
                    if json_file in archive.namelist():
                        # Read file content
                        file_bytes = archive.read(json_file)
                        file_size = len(file_bytes)

                        # Security: Check file size
                        is_ok, size_error = memory_mgr.check_file_size(file_size)
                        if not is_ok:
                            warnings.append(size_error)
                            continue

                        content = file_bytes.decode("utf-8")

                        # Parse with memory limits
                        if json_file == "tokens.json":
                            parsed_data, load_error = StreamingJsonLoader.load_tokens_streaming(
                                content, max_tokens=memory_mgr.max_tokens
                            )
                            if load_error:
                                warnings.append(load_error)
                            data.update(parsed_data)
                        else:
                            try:
                                parsed = json.loads(content)
                                data.update(parsed)
                            except json.JSONDecodeError as e:
                                warnings.append(f"Error parsing {json_file}: {str(e)}")

                self.data = data

                # Referential integrity checks
                referential_errors = ArchiveValidator.validate_referential_integrity(data)
                errors.extend(referential_errors)

                # Build analysis
                return ImportAnalysis(
                    is_valid=len(errors) == 0,
                    errors=errors,
                    warnings=warnings,
                    schema_version=schema_version,
                    project_name=self.manifest.get("projectName", "Unknown"),
                    content_summary=self.manifest.get("contents", {}),
                    migration_needed=migration_needed,
                    target_version=current_version,
                )

        except Exception as e:
            return ImportAnalysis(
                is_valid=False,
                errors=[
                    ImportValidationError(
                        stage="archive",
                        message=f"Failed to open archive: {str(e)}",
                    )
                ],
                warnings=warnings,
                schema_version="unknown",
                project_name="unknown",
                content_summary={},
                migration_needed=False,
                target_version="",
            )

    def import_replace(self) -> Project:
        """
        Import with REPLACE strategy - replaces all project data

        Returns:
            Restored Project object

        Raises:
            ValueError: If archive is invalid
        """
        analysis = self.analyze()
        if not analysis.is_valid:
            error_msgs = "\n".join([f"- [{e.stage}] {e.message}" for e in analysis.errors])
            raise ValueError(f"Archive validation failed:\n{error_msgs}")

        # Apply migrations if needed
        if analysis.migration_needed:
            self.data = MigrationManager.migrate(
                self.data, analysis.schema_version, analysis.target_version
            )

        # Reconstruct project from archive data
        project_config = self.data.get("project", {})

        # Create project
        project = Project(
            id=project_config.get("id", "imported-project"),
            uuid=project_config.get("uuid"),
            name=project_config.get("name", "Imported Project"),
            version=project_config.get("version", "1.0.0"),
            description=project_config.get("description"),
            theme=self._build_theme(),
            components=self._build_components(),
            metadata=ProjectMetadata(
                author=project_config.get("author"),
                team=project_config.get("team"),
                tags=project_config.get("tags", []),
            ),
        )

        return project

    def _build_theme(self) -> Theme:
        """Build Theme from archive data"""
        tokens_data = self.data.get("tokens", {})
        themes_raw = self.data.get("themes", {})
        # Handle both dict and list cases
        themes_data = themes_raw.get("themes", []) if isinstance(themes_raw, dict) else (themes_raw if isinstance(themes_raw, list) else [])

        # Build tokens dict
        tokens_dict = {}
        for token_name, token_data in tokens_data.items():
            tokens_dict[token_name] = self._deserialize_token(token_data)

        # Get theme from themes list or create default
        theme_config = themes_data[0] if themes_data else {}

        return Theme(
            uuid=theme_config.get("uuid"),
            name=theme_config.get("name", "Default"),
            version=theme_config.get("version", "1.0.0"),
            tokens=tokens_dict,
        )

    def _build_components(self) -> List[Component]:
        """Build components from archive data"""
        components_raw = self.data.get("components", {})
        # Handle both dict and list cases
        components_data = components_raw.get("components", []) if isinstance(components_raw, dict) else (components_raw if isinstance(components_raw, list) else [])
        components = []

        for comp_data in components_data:
            components.append(self._deserialize_component(comp_data))

        return components

    @staticmethod
    def _deserialize_token(token_data: Dict[str, Any]) -> DesignToken:
        """Deserialize token from archive format"""
        return DesignToken(
            uuid=token_data.get("uuid"),
            name=token_data.get("name", ""),
            value=token_data.get("$value"),
            type=token_data.get("$type", "string"),
            category=TokenCategory(token_data.get("$category", "other")),
            description=token_data.get("$description"),
            source=token_data.get("$source"),
            deprecated=token_data.get("$deprecated", False),
            created_at=_parse_datetime(token_data.get("$createdAt")),
            updated_at=_parse_datetime(token_data.get("$updatedAt")),
        )

    @staticmethod
    def _deserialize_component(comp_data: Dict[str, Any]) -> Component:
        """Deserialize component from archive format"""
        return Component(
            uuid=comp_data.get("uuid"),
            name=comp_data.get("name", ""),
            source=comp_data.get("source", "custom"),
            description=comp_data.get("description"),
            variants=comp_data.get("variants", []),
            props=comp_data.get("props", {}),
            dependencies=comp_data.get("dependencies", []),
        )


def _is_valid_version(version: str) -> bool:
    """Check if version string matches semantic versioning"""
    parts = version.split(".")
    if len(parts) != 3:
        return False
    return all(part.isdigit() for part in parts)


def _parse_datetime(dt_str: Optional[str]) -> datetime:
    """Parse ISO datetime string"""
    if not dt_str:
        return datetime.utcnow()
    try:
        # Handle with Z suffix
        if dt_str.endswith("Z"):
            dt_str = dt_str[:-1] + "+00:00"
        return datetime.fromisoformat(dt_str)
    except (ValueError, TypeError):
        return datetime.utcnow()


# Export
__all__ = ["DSSArchiveImporter", "ArchiveValidator", "ImportAnalysis", "ImportValidationError"]
