from __future__ import annotations

import csv
import json
import re
import secrets
import shutil
from datetime import datetime
from pathlib import Path
from typing import Iterable

from .models import Detection, Profile


APP_DIR = Path.home() / ".safety_judge_v13"
PROFILES_DIR = APP_DIR / "profiles"
APP_SETTINGS_FILE = APP_DIR / "app_settings.json"


def valid_email(email: str) -> bool:
    email = email.strip()
    if not email:
        return True
    return bool(re.fullmatch(r"[^\s@]+@[^\s@]+\.[^\s@]+", email))


def safe_write_json(path: Path, data: object) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    temp = path.with_suffix(path.suffix + ".tmp")
    temp.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
    temp.replace(path)


class ProfileStore:
    def __init__(self, base_dir: Path = APP_DIR) -> None:
        self.base_dir = base_dir
        self.profiles_dir = base_dir / "profiles"
        self.settings_file = base_dir / "app_settings.json"
        self.profiles_dir.mkdir(parents=True, exist_ok=True)

    def list_profiles(self) -> list[Profile]:
        profiles: list[Profile] = []
        for path in sorted(self.profiles_dir.glob("*/profile.json")):
            try:
                profiles.append(Profile.from_dict(json.loads(path.read_text(encoding="utf-8"))))
            except (OSError, json.JSONDecodeError, TypeError, ValueError):
                continue
        profiles.sort(key=lambda p: (p.display_name.lower(), p.created_at))
        return profiles

    def create_profile(self, display_name: str, report_email: str, avatar_color: str) -> Profile:
        name = " ".join(display_name.strip().split())
        if not name:
            raise ValueError("Profile name is required.")
        if not valid_email(report_email):
            raise ValueError("Enter a valid email address or leave it blank.")
        profile_id = f"{datetime.now():%Y%m%d%H%M%S}-{secrets.token_hex(3)}"
        profile = Profile(
            profile_id=profile_id,
            display_name=name,
            report_email=report_email.strip().lower(),
            avatar_color=avatar_color,
        )
        self.save_profile(profile)
        return profile

    def save_profile(self, profile: Profile) -> None:
        profile.updated_at = datetime.now().isoformat(timespec="seconds")
        safe_write_json(self.profile_dir(profile.profile_id) / "profile.json", profile.to_dict())

    def delete_profile(self, profile_id: str) -> None:
        path = self.profile_dir(profile_id)
        if path.exists():
            shutil.rmtree(path)
        settings = self.load_app_settings()
        if settings.get("last_profile_id") == profile_id:
            settings["last_profile_id"] = ""
            self.save_app_settings(settings)

    def profile_dir(self, profile_id: str) -> Path:
        return self.profiles_dir / profile_id

    def reports_file(self, profile_id: str) -> Path:
        return self.profile_dir(profile_id) / "reports.jsonl"

    def load_app_settings(self) -> dict:
        try:
            return json.loads(self.settings_file.read_text(encoding="utf-8"))
        except (FileNotFoundError, json.JSONDecodeError):
            return {"last_profile_id": "", "remember_last_profile": True}

    def save_app_settings(self, settings: dict) -> None:
        safe_write_json(self.settings_file, settings)

    def set_last_profile(self, profile_id: str) -> None:
        settings = self.load_app_settings()
        settings["last_profile_id"] = profile_id
        self.save_app_settings(settings)

    def get_profile(self, profile_id: str) -> Profile | None:
        path = self.profile_dir(profile_id) / "profile.json"
        try:
            return Profile.from_dict(json.loads(path.read_text(encoding="utf-8")))
        except (FileNotFoundError, json.JSONDecodeError, TypeError, ValueError):
            return None


class ReportStore:
    def __init__(self, profile_store: ProfileStore) -> None:
        self.profile_store = profile_store

    def append(self, profile_id: str, detections: Iterable[Detection]) -> int:
        path = self.profile_store.reports_file(profile_id)
        path.parent.mkdir(parents=True, exist_ok=True)
        count = 0
        with path.open("a", encoding="utf-8") as handle:
            for detection in detections:
                handle.write(json.dumps(detection.to_dict(), ensure_ascii=False) + "\n")
                count += 1
        return count

    def read(self, profile_id: str, limit: int | None = None) -> list[Detection]:
        path = self.profile_store.reports_file(profile_id)
        results: list[Detection] = []
        try:
            lines = path.read_text(encoding="utf-8").splitlines()
        except FileNotFoundError:
            return []
        if limit is not None:
            lines = lines[-limit:]
        for line in lines:
            try:
                results.append(Detection.from_dict(json.loads(line)))
            except (json.JSONDecodeError, TypeError, ValueError):
                continue
        return results

    def replace(self, profile_id: str, detections: Iterable[Detection]) -> None:
        path = self.profile_store.reports_file(profile_id)
        path.parent.mkdir(parents=True, exist_ok=True)
        temp = path.with_suffix(".tmp")
        with temp.open("w", encoding="utf-8") as handle:
            for detection in detections:
                handle.write(json.dumps(detection.to_dict(), ensure_ascii=False) + "\n")
        temp.replace(path)

    def clear(self, profile_id: str) -> None:
        self.profile_store.reports_file(profile_id).unlink(missing_ok=True)

    def export_csv(self, profile_id: str, destination: Path) -> int:
        detections = self.read(profile_id)
        destination.parent.mkdir(parents=True, exist_ok=True)
        with destination.open("w", newline="", encoding="utf-8") as handle:
            writer = csv.writer(handle)
            writer.writerow([
                "timestamp",
                "category",
                "risk",
                "text",
                "reason",
                "source",
                "application",
                "x",
                "y",
                "width",
                "height",
            ])
            for item in detections:
                writer.writerow([
                    item.timestamp,
                    item.category,
                    f"{item.risk:.4f}",
                    item.text,
                    item.reason,
                    item.source,
                    item.app_hint,
                    item.x,
                    item.y,
                    item.width,
                    item.height,
                ])
        return len(detections)

    def stats(self, profile_id: str) -> dict[str, int]:
        detections = self.read(profile_id)
        result: dict[str, int] = {"total": len(detections), "high_risk": 0}
        for detection in detections:
            result[detection.category] = result.get(detection.category, 0) + 1
            if detection.risk >= 0.9:
                result["high_risk"] += 1
        return result
