"""Email yeniden kayıt bilgilerini yöneten yardımcı modül."""
from __future__ import annotations

import json
import os
import threading
import time
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import timedelta
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Set

try:
    import fcntl  # type: ignore
except ImportError:  # pragma: no cover
    fcntl = None
try:
    import msvcrt  # type: ignore
except ImportError:  # pragma: no cover
    msvcrt = None

from email_utils import normalize_email_key, randomize_gmail_local_part


def _safe_int(value: Any, default: int = 0) -> int:
    try:
        return int(value)
    except (TypeError, ValueError):
        return default


def _ensure_directory(path: Path) -> None:
    try:
        path.parent.mkdir(parents=True, exist_ok=True)
    except OSError:
        pass


@contextmanager
def _interprocess_lock(path: Path):
    """Basit dosya kilidi; process'ler arası eşzamanlama için kullanılır."""

    _ensure_directory(path)
    handle = path.open("a+")
    try:
        if fcntl is not None:
            fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
        elif msvcrt is not None:
            msvcrt.locking(handle.fileno(), msvcrt.LK_LOCK, 1)
        yield
    finally:
        try:
            if fcntl is not None:
                fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
            elif msvcrt is not None:
                msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
        finally:
            try:
                handle.close()
            except Exception:
                pass


@dataclass
class EmailReRegistrationEntry:
    """Email_information_awaiting_re-registration.txt içindeki tek kayıt."""

    payload: Dict[str, Any]

    @property
    def email_id(self) -> str:
        return str(self.payload.get("email_id") or "").strip()

    @property
    def email_address(self) -> str:
        return str(self.payload.get("email_address") or "").strip()

    @property
    def normalized_email(self) -> str:
        return normalize_email_key(self.email_address)

    @property
    def count(self) -> int:
        return _safe_int(self.payload.get("count"), 0)

    @property
    def last_used(self) -> int:
        return _safe_int(self.payload.get("last_used"), 0)

    def with_last_used(self, timestamp: int) -> "EmailReRegistrationEntry":
        updated = dict(self.payload)
        updated["last_used"] = int(timestamp)
        return EmailReRegistrationEntry(updated)

    def with_usage(self, timestamp: int) -> "EmailReRegistrationEntry":
        """Son kullanım zamanını ve sayaç değerini güncellenmiş kopya olarak döndür."""

        updated = dict(self.payload)
        updated["last_used"] = int(timestamp)
        current_count = _safe_int(updated.get("count"), 0)
        if current_count <= 0:
            updated["count"] = 1
        else:
            updated["count"] = current_count + 1
        return EmailReRegistrationEntry(updated)

    @property
    def used_dot_positions(self) -> List[List[int]]:
        history: List[List[int]] = []
        raw_value = self.payload.get("used_dot_positions")
        if isinstance(raw_value, list):
            for item in raw_value:
                if not isinstance(item, Iterable):
                    continue
                try:
                    values = [int(value) for value in item]
                except (TypeError, ValueError):
                    continue
                cleaned = sorted({value for value in values if value >= 0})
                if cleaned:
                    history.append(list(cleaned))
        return history

    def with_used_dot_positions(
        self, positions: List[List[int]]
    ) -> "EmailReRegistrationEntry":
        updated = dict(self.payload)
        if positions:
            normalized: List[List[int]] = []
            for item in positions:
                cleaned_set: Set[int] = set()
                for value in item:
                    try:
                        numeric = int(value)
                    except (TypeError, ValueError):
                        continue
                    if numeric >= 0:
                        cleaned_set.add(numeric)
                if cleaned_set:
                    normalized.append(sorted(cleaned_set))
            if normalized:
                updated["used_dot_positions"] = normalized
            elif "used_dot_positions" in updated:
                updated.pop("used_dot_positions", None)
        elif "used_dot_positions" in updated:
            updated.pop("used_dot_positions", None)
        return EmailReRegistrationEntry(updated)


class EmailReRegistrationManager:
    """Email yeniden kullanım kuyruğunu yöneten sınıf."""

    def __init__(
        self,
        path: Path,
        file_lock: Optional[threading.Lock] = None,
        *,
        reuse_after: timedelta,
        poll_interval: float = 60.0,
        max_uses: int = 1,
        usage_tracking: bool = True,
    ) -> None:
        self._path = Path(path)
        self._reuse_after = max(0, int(reuse_after.total_seconds()))
        self._poll_interval = max(1.0, float(poll_interval))
        self._max_uses = max(0, int(max_uses))
        self._usage_tracking = bool(usage_tracking)
        self._lock = threading.Lock()
        self._file_lock = file_lock or threading.Lock()
        self._queue: List[EmailReRegistrationEntry] = []
        self._last_poll = 0.0
        self._backup_path = self._path.with_name(
            f"{self._path.stem}_silinenler.txt"
        )
        self._lockfile_path = self._path.with_suffix(self._path.suffix + ".lock")

    def configure(
        self,
        *,
        max_uses: Optional[int] = None,
        reuse_after: Optional[timedelta] = None,
        usage_tracking: Optional[bool] = None,
    ) -> None:
        with self._lock:
            if max_uses is not None:
                self._max_uses = max(0, int(max_uses))
            if reuse_after is not None:
                self._reuse_after = max(0, int(reuse_after.total_seconds()))
            if usage_tracking is not None:
                self._usage_tracking = bool(usage_tracking)
            # Yeni parametrelerde eski kuyruk geçersiz sayılır
            self._queue.clear()
            self._last_poll = 0.0

    def acquire_ready_email(self) -> Optional[Dict[str, Any]]:
        with self._lock:
            self._poll_if_needed()
            while self._queue:
                entry = self._queue.pop(0)
                if self._reserve_entry(entry):
                    return dict(entry.payload)
        return None

    def count_ready_emails(self) -> int:
        """Hazir (reuse suresi dolmus) kayitlarin sayisini dondur."""

        with self._lock:
            self._queue = self._collect_ready_entries(time.time())
            return len(self._queue)

    def has_ready_email(self) -> bool:
        """Kuyrukta kullanilabilir e-posta olup olmadigini belirt."""

        with self._lock:
            self._poll_if_needed()
            return bool(self._queue)

    def _poll_if_needed(self) -> None:
        now = time.time()
        if now - self._last_poll < self._poll_interval:
            return
        self._last_poll = now
        self._queue = self._collect_ready_entries(now)

    def _collect_ready_entries(self, now: float) -> List[EmailReRegistrationEntry]:
        entries = self._read_entries()
        ready: List[EmailReRegistrationEntry] = []
        valid_entries: List[EmailReRegistrationEntry] = []
        changed = False
        seen_keys: Set[str] = set()

        for entry in entries:
            entry_id = entry.email_id
            normalized_email = entry.normalized_email

            dedup_key = ""
            if entry_id:
                dedup_key = f"id:{entry_id}"
            elif normalized_email:
                dedup_key = f"email:{normalized_email}"

            if dedup_key:
                if dedup_key in seen_keys:
                    self._store_removed_entry(entry, "yinelenen kayıt kaldırıldı")
                    changed = True
                    continue
                seen_keys.add(dedup_key)

            if self._usage_tracking and self._max_uses and entry.count >= self._max_uses:
                self._store_removed_entry(entry, "maksimum kullanım sınırına ulaşıldı")
                changed = True
                continue

            valid_entries.append(entry)

            if not entry.email_address:
                continue

            if self._usage_tracking and self._reuse_after:
                if now - entry.last_used < self._reuse_after:
                    continue

            ready.append(entry)

        if changed:
            self._write_entries(valid_entries)

        if self._usage_tracking:
            ready.sort(key=lambda item: item.last_used)
        return ready

    def _reserve_entry(self, entry: EmailReRegistrationEntry) -> bool:
        now = int(time.time())
        entries = self._read_entries()
        changed = False
        reserved = False
        target_id = entry.email_id
        target_key = entry.normalized_email
        updated_entries: List[EmailReRegistrationEntry] = []

        for current in entries:
            if self._usage_tracking and self._max_uses and current.count >= self._max_uses:
                self._store_removed_entry(
                    current, "maksimum kullanım sınırına ulaştığı için kaldırıldı"
                )
                changed = True
                continue

            current_id = current.email_id
            current_key = current.normalized_email

            matches_by_id = bool(target_id) and current_id == target_id
            matches_by_email = False
            if not target_id and target_key and current_key and current_key == target_key:
                matches_by_email = True

            if not reserved and (matches_by_id or matches_by_email):
                if self._usage_tracking and self._reuse_after:
                    if now - current.last_used < self._reuse_after:
                        updated_entries.append(current)
                        continue

                reserved = True

                if self._usage_tracking:
                    usage_entry = current.with_usage(now)
                    randomized_email, history = randomize_gmail_local_part(
                        current.email_address,
                        used_dot_positions=current.used_dot_positions,
                        advance_history=True,
                    )
                    usage_entry = usage_entry.with_used_dot_positions(history)
                    new_count = usage_entry.count
                    entry.payload = dict(usage_entry.payload)
                    if (
                        randomized_email
                        and randomized_email != current.email_address
                    ):
                        entry.payload["reserved_email_variant"] = randomized_email
                    else:
                        entry.payload.pop("reserved_email_variant", None)
                    if self._max_uses and new_count >= self._max_uses:
                        self._store_removed_entry(
                            usage_entry,
                            "kayıt kullanıldı ve maksimum sınırı geçtiği için kaldırıldı",
                        )
                        changed = True
                        continue
                    updated_entries.append(usage_entry)
                    changed = True
                else:
                    entry.payload = dict(current.payload)
                    self._store_removed_entry(
                        current, "kayıt kullanıldıktan sonra listeden çıkarıldı"
                    )
                    changed = True
                    continue
            else:
                updated_entries.append(current)

        if self._usage_tracking and (reserved or changed):
            self._write_entries(updated_entries)
        elif not self._usage_tracking and reserved:
            self._write_entries(updated_entries)

        return reserved

    def _read_entries(self) -> List[EmailReRegistrationEntry]:
        path = self._path
        if not path.exists():
            return []

        entries: List[EmailReRegistrationEntry] = []
        with self._file_lock, _interprocess_lock(self._lockfile_path):
            try:
                with path.open("r", encoding="utf-8") as handle:
                    for raw_line in handle:
                        line = raw_line.strip()
                        if not line:
                            continue
                        try:
                            payload = json.loads(line)
                        except json.JSONDecodeError:
                            continue
                        if isinstance(payload, dict):
                            entries.append(EmailReRegistrationEntry(payload))
            except OSError:
                return []

        return entries

    def _write_entries(self, entries: List[EmailReRegistrationEntry]) -> None:
        path = self._path
        with self._file_lock, _interprocess_lock(self._lockfile_path):
            if not entries:
                if path.exists():
                    try:
                        path.unlink()
                    except OSError:
                        pass
                return

            _ensure_directory(path)
            lines = [json.dumps(entry.payload, ensure_ascii=False) for entry in entries]
            tmp_path = path.with_suffix(path.suffix + ".tmp")
            try:
                with tmp_path.open("w", encoding="utf-8") as handle:
                    handle.write("\n".join(lines) + "\n")
                tmp_path.replace(path)
            except OSError:
                try:
                    if tmp_path.exists():
                        tmp_path.unlink()
                except OSError:
                    pass

    def _store_removed_entry(
        self, entry: EmailReRegistrationEntry, reason: str
    ) -> None:
        """Silinen kayıtları anlaşılır bir günlükle farklı dosyada sakla."""

        backup_payload = {
            "silme_zamani": int(time.time()),
            "neden": reason,
            "kayıt": entry.payload,
        }
        _ensure_directory(self._backup_path)
        with self._file_lock, _interprocess_lock(self._lockfile_path):
            try:
                with self._backup_path.open("a", encoding="utf-8") as handle:
                    handle.write(json.dumps(backup_payload, ensure_ascii=False) + "\n")
            except OSError:
                # Burada hata bastırılıyor çünkü asıl iş akışı durmasın; yine de
                # kayıt silinmiş olduğu için bilgi kaybını en aza indirmeye çalışıyoruz.
                pass
