"""Persistence for security domain-inspired patterns state."""

from __future__ import annotations

import os
import stat
from pathlib import Path

import yaml

from gaol.config import get_data_dir
from gaol.domains.models import (
    ChainState,
    ChainStore,
    ClientAttachment,
    DisposableConfig,
    DisposableInstance,
    DisposableStore,
    DomainPolicy,
    DomainStore,
    SecurityDomain,
    ServiceContainerState,
    ServiceContainerStore,
    ServiceContainerType,
    TemplateState,
    TemplateStore,
    VaultAttachment,
    VaultPolicy,
    VaultState,
    VaultStore,
    VaultType,
)


def _get_domains_dir() -> Path:
    """Get the domains data directory."""
    return get_data_dir() / "domains"


class DomainsFileStore:
    """Persistent storage for domain-related state.

    Stores templates, disposables, and domains in YAML files
    with restrictive permissions.
    """

    def __init__(self, store_dir: Path | None = None) -> None:
        """Initialize the store."""
        self._store_dir = store_dir or _get_domains_dir()
        self._templates_path = self._store_dir / "templates.yaml"
        self._disposables_path = self._store_dir / "disposables.yaml"
        self._domains_path = self._store_dir / "domains.yaml"
        self._services_path = self._store_dir / "services.yaml"
        self._vaults_path = self._store_dir / "vaults.yaml"
        self._chains_path = self._store_dir / "chains.yaml"

    def _ensure_dir(self) -> None:
        """Ensure store directory exists with proper permissions."""
        self._store_dir.mkdir(parents=True, exist_ok=True)
        os.chmod(self._store_dir, stat.S_IRWXU)  # 700

    # Template operations

    def _load_templates(self) -> TemplateStore:
        """Load templates from disk."""
        if not self._templates_path.exists():
            return TemplateStore()

        with open(self._templates_path) as f:
            data = yaml.safe_load(f) or {}

        return TemplateStore.model_validate(data)

    def _save_templates(self, store: TemplateStore) -> None:
        """Save templates to disk."""
        self._ensure_dir()

        temp_path = self._templates_path.with_suffix(".tmp")
        with open(temp_path, "w") as f:
            yaml.dump(store.model_dump(mode="json"), f, default_flow_style=False)

        os.chmod(temp_path, stat.S_IRUSR | stat.S_IWUSR)  # 600
        temp_path.rename(self._templates_path)

    def save_template(self, template: TemplateState) -> None:
        """Save or update a template."""
        store = self._load_templates()
        store.add_template(template)
        self._save_templates(store)

    def get_template(self, name: str) -> TemplateState | None:
        """Get a template by name."""
        store = self._load_templates()
        return store.get_template(name)

    def delete_template(self, name: str) -> bool:
        """Delete a template."""
        store = self._load_templates()
        if store.remove_template(name):
            self._save_templates(store)
            return True
        return False

    def list_templates(self) -> list[TemplateState]:
        """List all templates."""
        store = self._load_templates()
        return store.list_templates()

    def template_exists(self, name: str) -> bool:
        """Check if a template exists."""
        return self.get_template(name) is not None

    def add_template_instance(self, template_name: str, instance_name: str) -> None:
        """Track an instance spawned from a template."""
        store = self._load_templates()
        template = store.get_template(template_name)
        if template and instance_name not in template.instances:
            template.instances.append(instance_name)
            self._save_templates(store)

    def remove_template_instance(self, template_name: str, instance_name: str) -> None:
        """Remove instance tracking from a template."""
        store = self._load_templates()
        template = store.get_template(template_name)
        if template and instance_name in template.instances:
            template.instances.remove(instance_name)
            self._save_templates(store)

    # Disposable operations

    def _load_disposables(self) -> DisposableStore:
        """Load disposables from disk."""
        if not self._disposables_path.exists():
            return DisposableStore()

        with open(self._disposables_path) as f:
            data = yaml.safe_load(f) or {}

        return DisposableStore.model_validate(data)

    def _save_disposables(self, store: DisposableStore) -> None:
        """Save disposables to disk."""
        self._ensure_dir()

        temp_path = self._disposables_path.with_suffix(".tmp")
        with open(temp_path, "w") as f:
            yaml.dump(store.model_dump(mode="json"), f, default_flow_style=False)

        os.chmod(temp_path, stat.S_IRUSR | stat.S_IWUSR)  # 600
        temp_path.rename(self._disposables_path)

    def save_disposable(self, config: DisposableConfig) -> None:
        """Save or update a disposable configuration."""
        store = self._load_disposables()
        store.add_disposable(config)
        self._save_disposables(store)

    def get_disposable(self, name: str) -> DisposableConfig | None:
        """Get a disposable by name."""
        store = self._load_disposables()
        return store.get_disposable(name)

    def delete_disposable(self, name: str) -> bool:
        """Delete a disposable configuration."""
        store = self._load_disposables()
        if store.remove_disposable(name):
            self._save_disposables(store)
            return True
        return False

    def list_disposables(self) -> list[DisposableConfig]:
        """List all disposable configurations."""
        store = self._load_disposables()
        return store.list_disposables()

    def disposable_exists(self, name: str) -> bool:
        """Check if a disposable exists."""
        return self.get_disposable(name) is not None

    def add_disposable_instance(self, instance: DisposableInstance) -> None:
        """Track a running disposable instance."""
        store = self._load_disposables()
        store.add_instance(instance)
        self._save_disposables(store)

    def remove_disposable_instance(self, instance_id: str) -> bool:
        """Remove a disposable instance tracking."""
        store = self._load_disposables()
        if store.remove_instance(instance_id):
            self._save_disposables(store)
            return True
        return False

    def list_disposable_instances(
        self, disposable_name: str | None = None
    ) -> list[DisposableInstance]:
        """List running disposable instances."""
        store = self._load_disposables()
        return store.list_instances(disposable_name)

    # Domain operations

    def _load_domains(self) -> DomainStore:
        """Load domains from disk."""
        if not self._domains_path.exists():
            return DomainStore()

        with open(self._domains_path) as f:
            data = yaml.safe_load(f) or {}

        return DomainStore.model_validate(data)

    def _save_domains(self, store: DomainStore) -> None:
        """Save domains to disk."""
        self._ensure_dir()

        temp_path = self._domains_path.with_suffix(".tmp")
        with open(temp_path, "w") as f:
            yaml.dump(store.model_dump(mode="json"), f, default_flow_style=False)

        os.chmod(temp_path, stat.S_IRUSR | stat.S_IWUSR)  # 600
        temp_path.rename(self._domains_path)

    def get_domain_policy(self, domain: SecurityDomain) -> DomainPolicy:
        """Get policy for a domain."""
        store = self._load_domains()
        return store.get_policy(domain)

    def set_domain_policy(self, policy: DomainPolicy) -> None:
        """Set a custom policy for a domain."""
        store = self._load_domains()
        store.set_custom_policy(policy)
        self._save_domains(store)

    def assign_container_domain(
        self, container_name: str, domain: SecurityDomain
    ) -> None:
        """Assign a container to a domain."""
        store = self._load_domains()
        store.assign_container(container_name, domain)
        self._save_domains(store)

    def get_container_domain(self, container_name: str) -> SecurityDomain | None:
        """Get the domain assigned to a container."""
        store = self._load_domains()
        return store.get_container_domain(container_name)

    def unassign_container_domain(self, container_name: str) -> bool:
        """Remove a container's domain assignment."""
        store = self._load_domains()
        if store.unassign_container(container_name):
            self._save_domains(store)
            return True
        return False

    def list_containers_in_domain(self, domain: SecurityDomain) -> list[str]:
        """List all containers in a domain."""
        store = self._load_domains()
        return store.list_containers_in_domain(domain)

    # Service container operations

    def _load_services(self) -> ServiceContainerStore:
        """Load services from disk."""
        if not self._services_path.exists():
            return ServiceContainerStore()

        with open(self._services_path) as f:
            data = yaml.safe_load(f) or {}

        return ServiceContainerStore.model_validate(data)

    def _save_services(self, store: ServiceContainerStore) -> None:
        """Save services to disk."""
        self._ensure_dir()

        temp_path = self._services_path.with_suffix(".tmp")
        with open(temp_path, "w") as f:
            yaml.dump(store.model_dump(mode="json"), f, default_flow_style=False)

        os.chmod(temp_path, stat.S_IRUSR | stat.S_IWUSR)  # 600
        temp_path.rename(self._services_path)

    def save_service(self, service: ServiceContainerState) -> None:
        """Save or update a service container."""
        store = self._load_services()
        store.add_service(service)
        self._save_services(store)

    def get_service(self, name: str) -> ServiceContainerState | None:
        """Get a service by name."""
        store = self._load_services()
        return store.get_service(name)

    def delete_service(self, name: str) -> bool:
        """Delete a service container."""
        store = self._load_services()
        if store.remove_service(name):
            self._save_services(store)
            return True
        return False

    def list_services(
        self, service_type: ServiceContainerType | None = None
    ) -> list[ServiceContainerState]:
        """List all service containers."""
        store = self._load_services()
        return store.list_services(service_type)

    def service_exists(self, name: str) -> bool:
        """Check if a service exists."""
        return self.get_service(name) is not None

    def add_client_attachment(self, attachment: ClientAttachment) -> None:
        """Track a client attachment to a service."""
        store = self._load_services()
        store.add_attachment(attachment)
        # Also update the service's client list
        service = store.get_service(attachment.service_name)
        if service and attachment.client_name not in service.attached_clients:
            service.attached_clients.append(attachment.client_name)
        self._save_services(store)

    def remove_client_attachment(self, client_name: str, service_name: str) -> bool:
        """Remove a client attachment from a service."""
        store = self._load_services()
        if store.remove_attachment(client_name, service_name):
            # Also update the service's client list
            service = store.get_service(service_name)
            if service and client_name in service.attached_clients:
                service.attached_clients.remove(client_name)
            self._save_services(store)
            return True
        return False

    def get_client_services(self, client_name: str) -> list[ClientAttachment]:
        """Get all services a client is attached to."""
        store = self._load_services()
        return store.get_client_attachments(client_name)

    def get_service_clients(self, service_name: str) -> list[ClientAttachment]:
        """Get all clients attached to a service."""
        store = self._load_services()
        return store.get_service_clients(service_name)

    # Vault operations

    def _load_vaults(self) -> VaultStore:
        """Load vaults from disk."""
        if not self._vaults_path.exists():
            return VaultStore()

        with open(self._vaults_path) as f:
            data = yaml.safe_load(f) or {}

        return VaultStore.model_validate(data)

    def _save_vaults(self, store: VaultStore) -> None:
        """Save vaults to disk."""
        self._ensure_dir()

        temp_path = self._vaults_path.with_suffix(".tmp")
        with open(temp_path, "w") as f:
            yaml.dump(store.model_dump(mode="json"), f, default_flow_style=False)

        os.chmod(temp_path, stat.S_IRUSR | stat.S_IWUSR)  # 600
        temp_path.rename(self._vaults_path)

    def save_vault(self, vault: VaultState) -> None:
        """Save or update a vault."""
        store = self._load_vaults()
        store.add_vault(vault)
        self._save_vaults(store)

    def get_vault(self, name: str) -> VaultState | None:
        """Get a vault by name."""
        store = self._load_vaults()
        return store.get_vault(name)

    def delete_vault(self, name: str) -> bool:
        """Delete a vault."""
        store = self._load_vaults()
        if store.remove_vault(name):
            self._save_vaults(store)
            return True
        return False

    def list_vaults(self, vault_type: VaultType | None = None) -> list[VaultState]:
        """List all vaults."""
        store = self._load_vaults()
        return store.list_vaults(vault_type)

    def vault_exists(self, name: str) -> bool:
        """Check if a vault exists."""
        return self.get_vault(name) is not None

    def save_vault_policy(self, policy: VaultPolicy) -> None:
        """Save or update a vault policy."""
        store = self._load_vaults()
        store.add_policy(policy)
        self._save_vaults(store)

    def get_vault_policy(
        self, container_name: str, vault_name: str
    ) -> VaultPolicy | None:
        """Get policy for a container-vault pair."""
        store = self._load_vaults()
        return store.get_policy(container_name, vault_name)

    def delete_vault_policy(self, container_name: str, vault_name: str) -> bool:
        """Delete a vault policy."""
        store = self._load_vaults()
        if store.remove_policy(container_name, vault_name):
            self._save_vaults(store)
            return True
        return False

    def list_vault_policies(self, vault_name: str | None = None) -> list[VaultPolicy]:
        """List all vault policies."""
        store = self._load_vaults()
        return store.list_policies(vault_name)

    def get_container_vault_policies(self, container_name: str) -> list[VaultPolicy]:
        """Get all policies for a specific container."""
        store = self._load_vaults()
        return store.get_container_policies(container_name)

    def add_vault_attachment(self, attachment: VaultAttachment) -> None:
        """Track a container attachment to a vault."""
        store = self._load_vaults()
        store.add_attachment(attachment)
        # Also update the vault's attached containers list
        vault = store.get_vault(attachment.vault_name)
        if vault and attachment.container_name not in vault.attached_containers:
            vault.attached_containers.append(attachment.container_name)
        self._save_vaults(store)

    def remove_vault_attachment(self, container_name: str, vault_name: str) -> bool:
        """Remove a container attachment from a vault."""
        store = self._load_vaults()
        if store.remove_attachment(container_name, vault_name):
            # Also update the vault's attached containers list
            vault = store.get_vault(vault_name)
            if vault and container_name in vault.attached_containers:
                vault.attached_containers.remove(container_name)
            self._save_vaults(store)
            return True
        return False

    def get_vault_attachment(
        self, container_name: str, vault_name: str
    ) -> VaultAttachment | None:
        """Get attachment for a container-vault pair."""
        store = self._load_vaults()
        return store.get_attachment(container_name, vault_name)

    def list_vault_attachments(
        self, vault_name: str | None = None
    ) -> list[VaultAttachment]:
        """List vault attachments."""
        store = self._load_vaults()
        return store.list_attachments(vault_name)

    # Chain operations

    def _load_chains(self) -> ChainStore:
        """Load chains from disk."""
        if not self._chains_path.exists():
            return ChainStore()

        with open(self._chains_path) as f:
            data = yaml.safe_load(f) or {}

        return ChainStore.model_validate(data)

    def _save_chains(self, store: ChainStore) -> None:
        """Save chains to disk."""
        self._ensure_dir()

        temp_path = self._chains_path.with_suffix(".tmp")
        with open(temp_path, "w") as f:
            yaml.dump(store.model_dump(mode="json"), f, default_flow_style=False)

        os.chmod(temp_path, stat.S_IRUSR | stat.S_IWUSR)  # 600
        temp_path.rename(self._chains_path)

    def save_chain(self, chain: ChainState) -> None:
        """Save or update a chain."""
        store = self._load_chains()
        store.add_chain(chain)
        self._save_chains(store)

    def get_chain(self, name: str) -> ChainState | None:
        """Get a chain by name."""
        store = self._load_chains()
        return store.get_chain(name)

    def delete_chain(self, name: str) -> bool:
        """Delete a chain."""
        store = self._load_chains()
        if store.remove_chain(name):
            self._save_chains(store)
            return True
        return False

    def list_chains(self) -> list[ChainState]:
        """List all chains."""
        store = self._load_chains()
        return store.list_chains()

    def chain_exists(self, name: str) -> bool:
        """Check if a chain exists."""
        return self.get_chain(name) is not None

    def get_chains_for_container(self, container_name: str) -> list[ChainState]:
        """Get all chains involving a container."""
        store = self._load_chains()
        return store.get_chains_for_container(container_name)


# Global instance
_store: DomainsFileStore | None = None


def get_domain_store() -> DomainsFileStore:
    """Get the global DomainsFileStore instance."""
    global _store
    if _store is None:
        _store = DomainsFileStore()
    return _store
