"""
File Service — Business Logic Services.

سرویس مرکزی مدیریت فایل‌ها شامل:
- آپلود با deduplication
- ذخیره‌سازی با filesystem abstraction
- تولید signed URL برای دانلود
- مدیریت ارجاع‌ها
"""
import hashlib
import hmac
import logging
import os
import time
import uuid
from pathlib import Path
from typing import Optional

from django.conf import settings
from django.db import transaction
from django.utils import timezone

from .models import FileReference, FileStatus, ReferenceContext, StoredFile

logger = logging.getLogger('apps')


# ──────────────────────────────────────────────────────────────
# Storage Backend (filesystem abstraction)
# ──────────────────────────────────────────────────────────────

class LocalStorageBackend:
    """
    ذخیره‌سازی فایل در فایل‌سیستم محلی.
    ساختار: MEDIA_ROOT/files/{tenant_id}/{YYYY}/{MM}/{uuid}.{ext}
    در آینده قابل جایگزینی با S3Backend, MinIOBackend.
    """

    def __init__(self):
        self.base_path = Path(settings.MEDIA_ROOT) / 'files'

    def _build_path(self, tenant_id: uuid.UUID, file_id: uuid.UUID, ext: str) -> str:
        """ساخت مسیر نسبی فایل."""
        now = timezone.now()
        relative = f"{tenant_id}/{now.year}/{now.month:02d}/{file_id}.{ext}"
        return relative

    def save(self, tenant_id: uuid.UUID, file_id: uuid.UUID, ext: str, file_obj) -> str:
        """ذخیره فایل و بازگشت مسیر نسبی."""
        relative_path = self._build_path(tenant_id, file_id, ext)
        full_path = self.base_path / relative_path
        full_path.parent.mkdir(parents=True, exist_ok=True)

        with open(full_path, 'wb') as dest:
            for chunk in file_obj.chunks():
                dest.write(chunk)

        return relative_path

    def delete(self, relative_path: str) -> bool:
        """حذف فایل فیزیکی."""
        full_path = self.base_path / relative_path
        try:
            if full_path.exists():
                full_path.unlink()
                return True
        except OSError as e:
            logger.error(f"Failed to delete file {relative_path}: {e}")
        return False

    def exists(self, relative_path: str) -> bool:
        """بررسی وجود فایل."""
        return (self.base_path / relative_path).exists()

    def get_full_path(self, relative_path: str) -> Path:
        """بازگشت مسیر کامل فایل."""
        return self.base_path / relative_path


# ──────────────────────────────────────────────────────────────
# File Storage Service
# ──────────────────────────────────────────────────────────────

class FileStorageService:
    """سرویس مرکزی آپلود/دانلود/حذف فایل."""

    def __init__(self):
        self.backend = LocalStorageBackend()

    @transaction.atomic
    def upload(self, tenant, user, file_obj, context: str = '', context_id=None) -> StoredFile:
        """
        آپلود فایل با deduplication.
        اگر فایل با همان checksum وجود داشته باشد، فقط ارجاع جدید ایجاد می‌شود.
        """
        # محاسبه checksum
        checksum = self._compute_checksum(file_obj)

        # بررسی وجود فایل تکراری
        existing = StoredFile.objects.filter(
            tenant=tenant,
            checksum=checksum,
            status=FileStatus.ACTIVE,
        ).first()

        if existing:
            logger.info(f"File deduplicated: {file_obj.name} → existing {existing.id}")
            stored_file = existing
        else:
            # ذخیره فایل جدید
            file_id = uuid.uuid4()
            ext = self._get_extension(file_obj.name)

            storage_path = self.backend.save(tenant.id, file_id, ext, file_obj)

            stored_file = StoredFile.objects.create(
                id=file_id,
                tenant=tenant,
                original_name=file_obj.name,
                mime_type=getattr(file_obj, 'content_type', ''),
                size=file_obj.size,
                extension=ext,
                storage_path=storage_path,
                checksum=checksum,
                status=FileStatus.ACTIVE,
                uploaded_by=user,
            )

        # ایجاد ارجاع اگر context داده شده
        if context and context_id:
            FileReference.objects.create(
                tenant=tenant,
                stored_file=stored_file,
                context=context,
                context_id=context_id,
                display_name=file_obj.name,
                created_by=user,
            )
            stored_file.reference_count = stored_file.references.filter(is_active=True).count()
            stored_file.save(update_fields=['reference_count'])

        return stored_file

    def get_download_path(self, stored_file: StoredFile) -> Optional[Path]:
        """بازگشت مسیر فیزیکی فایل برای دانلود."""
        if stored_file.status != FileStatus.ACTIVE:
            return None
        return self.backend.get_full_path(stored_file.storage_path)

    def generate_signed_url(self, stored_file: StoredFile, expires_in: int = 3600) -> str:
        """
        تولید URL امن موقت برای دانلود.
        expires_in: مدت اعتبار به ثانیه (پیش‌فرض ۱ ساعت).
        """
        timestamp = int(time.time()) + expires_in
        payload = f"{stored_file.id}:{timestamp}"
        signature = hmac.new(
            settings.SECRET_KEY.encode(),
            payload.encode(),
            hashlib.sha256,
        ).hexdigest()[:32]
        return f"/api/v1/files/{stored_file.id}/download/?ts={timestamp}&sig={signature}"

    def verify_signed_url(self, file_id: str, timestamp: str, signature: str) -> bool:
        """اعتبارسنجی URL امن."""
        try:
            ts = int(timestamp)
            if ts < int(time.time()):
                return False  # منقضی شده
            payload = f"{file_id}:{ts}"
            expected = hmac.new(
                settings.SECRET_KEY.encode(),
                payload.encode(),
                hashlib.sha256,
            ).hexdigest()[:32]
            return hmac.compare_digest(signature, expected)
        except (ValueError, TypeError):
            return False

    @transaction.atomic
    def soft_delete(self, stored_file: StoredFile) -> None:
        """حذف نرم فایل."""
        stored_file.status = FileStatus.DELETED
        stored_file.save(update_fields=['status', 'updated_at'])
        # غیرفعال کردن همه ارجاع‌ها
        stored_file.references.update(is_active=False)

    def add_reference(self, tenant, user, stored_file: StoredFile,
                      context: str, context_id) -> FileReference:
        """اضافه کردن ارجاع جدید به فایل موجود."""
        ref = FileReference.objects.create(
            tenant=tenant,
            stored_file=stored_file,
            context=context,
            context_id=context_id,
            display_name=stored_file.original_name,
            created_by=user,
        )
        stored_file.reference_count = stored_file.references.filter(is_active=True).count()
        stored_file.save(update_fields=['reference_count'])
        return ref

    def remove_reference(self, reference: FileReference) -> None:
        """حذف ارجاع (بدون حذف فایل فیزیکی)."""
        reference.is_active = False
        reference.save(update_fields=['is_active'])
        sf = reference.stored_file
        sf.reference_count = sf.references.filter(is_active=True).count()
        sf.save(update_fields=['reference_count'])

    def get_references(self, context: str, context_id) -> list:
        """دریافت تمام فایل‌های مرتبط با یک context."""
        return FileReference.objects.filter(
            context=context,
            context_id=context_id,
            is_active=True,
        ).select_related('stored_file')

    @staticmethod
    def _compute_checksum(file_obj) -> str:
        sha256 = hashlib.sha256()
        for chunk in file_obj.chunks():
            sha256.update(chunk)
        file_obj.seek(0)
        return sha256.hexdigest()

    @staticmethod
    def _get_extension(filename: str) -> str:
        _, ext = os.path.splitext(filename)
        return ext.lstrip('.').lower() if ext else ''
