from __future__ import annotations

import csv
import json
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch

from order_sentry.config import load_config
from order_sentry.models import RunStats
from order_sentry.processor import RowValidationError, normalize_row, process_file


class NormalizeRowTests(unittest.TestCase):
    def setUp(self) -> None:
        self.config = load_config(None)

    def test_normalizes_valid_row(self) -> None:
        result = normalize_row(
            {
                "order_id": " ORD-1 ",
                "amount": "12.5",
                "status": "PAID",
                "created_at": "2026-07-18T10:00:00+08:00",
            },
            self.config,
            set(),
        )
        self.assertEqual(
            result,
            {
                "order_id": "ORD-1",
                "amount": "12.50",
                "status": "paid",
                "created_at": "2026-07-18T02:00:00Z",
            },
        )

    def test_duplicate_returns_none(self) -> None:
        seen = {"ORD-1"}
        result = normalize_row(
            {
                "order_id": "ORD-1",
                "amount": "12.5",
                "status": "paid",
                "created_at": "2026-07-18T10:00:00Z",
            },
            self.config,
            seen,
        )
        self.assertIsNone(result)

    def test_negative_amount_fails(self) -> None:
        with self.assertRaisesRegex(RowValidationError, "大于或等于零"):
            normalize_row(
                {
                    "order_id": "ORD-1",
                    "amount": "-1",
                    "status": "paid",
                    "created_at": "2026-07-18T10:00:00Z",
                },
                self.config,
                set(),
            )


class ProcessFileTests(unittest.TestCase):
    def test_partial_failure_and_report_files(self) -> None:
        config = load_config(None)
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            source = root / "input.csv"
            output = root / "output.csv"
            failures = root / "failures.jsonl"
            source.write_text(
                "order_id,amount,status,created_at\n"
                "ORD-1,10,paid,2026-07-18T10:00:00Z\n"
                "ORD-2,-1,paid,2026-07-18T10:00:00Z\n",
                encoding="utf-8",
            )
            stats = RunStats("TEST-1", source, output, False)
            process_file(source, output, failures, stats, config, overwrite=False)
            self.assertEqual((stats.total, stats.success, stats.failed), (2, 1, 1))
            with output.open(encoding="utf-8", newline="") as handle:
                rows = list(csv.DictReader(handle))
            self.assertEqual(len(rows), 1)
            failure = json.loads(failures.read_text(encoding="utf-8"))
            self.assertEqual(failure["category"], "invalid_amount")

    def test_empty_file_without_header_fails(self) -> None:
        config = load_config(None)
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            source = root / "empty.csv"
            source.write_text("", encoding="utf-8")
            stats = RunStats("TEST-2", source, root / "out.csv", True)
            with self.assertRaisesRegex(ValueError, "缺少字段头"):
                process_file(
                    source,
                    root / "out.csv",
                    root / "failures.jsonl",
                    stats,
                    config,
                    overwrite=False,
                )

    def test_overwrite_protection(self) -> None:
        config = load_config(None)
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            source = root / "input.csv"
            output = root / "output.csv"
            source.write_text(
                "order_id,amount,status,created_at\n",
                encoding="utf-8",
            )
            output.write_text("existing", encoding="utf-8")
            stats = RunStats("TEST-3", source, output, False)
            with self.assertRaises(FileExistsError):
                process_file(
                    source,
                    output,
                    root / "failures.jsonl",
                    stats,
                    config,
                    overwrite=False,
                )

    def test_dry_run_creates_no_business_files(self) -> None:
        config = load_config(None)
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            source = root / "input.csv"
            output = root / "output.csv"
            failures = root / "failures.jsonl"
            source.write_text(
                "order_id,amount,status,created_at\nORD-1,10,paid,2026-07-18T10:00:00Z\n",
                encoding="utf-8",
            )
            stats = RunStats("TEST-4", source, output, True)
            process_file(source, output, failures, stats, config, overwrite=False)
            self.assertEqual(stats.success, 1)
            self.assertFalse(output.exists())
            self.assertFalse(failures.exists())

    def test_permission_denied_is_classified(self) -> None:
        config = load_config(None)
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            source = root / "input.csv"
            source.write_text(
                "order_id,amount,status,created_at\n",
                encoding="utf-8",
            )
            stats = RunStats("TEST-5", source, root / "output.csv", False)
            with patch("order_sentry.processor.os.access", return_value=False):
                with self.assertRaisesRegex(PermissionError, "没有读取"):
                    process_file(
                        source,
                        root / "output.csv",
                        root / "failures.jsonl",
                        stats,
                        config,
                        overwrite=False,
                    )

    def test_interruption_removes_temporary_files(self) -> None:
        config = load_config(None)
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            source = root / "input.csv"
            output = root / "output.csv"
            failures = root / "failures.jsonl"
            source.write_text(
                "order_id,amount,status,created_at\nORD-1,10,paid,2026-07-18T10:00:00Z\n",
                encoding="utf-8",
            )
            stats = RunStats("TEST-6", source, output, False)
            with patch(
                "order_sentry.processor.normalize_row",
                side_effect=KeyboardInterrupt,
            ):
                with self.assertRaises(KeyboardInterrupt):
                    process_file(
                        source,
                        output,
                        failures,
                        stats,
                        config,
                        overwrite=False,
                    )
            self.assertEqual(stats.status, "interrupted")
            self.assertFalse(output.exists())
            self.assertFalse(failures.exists())
            self.assertEqual(list(root.glob("*.tmp")), [])


if __name__ == "__main__":
    unittest.main()
