"""
utils.csv_importer 단위 테스트.
"""
import os
import sys
import tempfile
from datetime import datetime
from pathlib import Path

import pytest

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from utils.csv_importer import parse_csv, _normalize_row, _normalize_time, import_records
from core.database import Database


class TestNormalizeTime:
    def test_hh_mm_to_hh_mm_ss(self):
        assert _normalize_time('09:00', 'clock_in') == '09:00:00'

    def test_hh_mm_ss_unchanged(self):
        assert _normalize_time('09:00:00', 'clock_in') == '09:00:00'

    def test_empty_raises(self):
        with pytest.raises(ValueError):
            _normalize_time('', 'clock_in')

    def test_invalid_format_raises(self):
        with pytest.raises(ValueError):
            _normalize_time('foo', 'clock_in')
        with pytest.raises(ValueError):
            _normalize_time('25:00', 'clock_in')


class TestNormalizeRow:
    def test_basic_row(self):
        row = {
            'date': '2026-04-01',
            'clock_in': '09:00',
            'clock_out': '18:00',
            'lunch_minutes': '60',
            'memo': '메모',
        }
        out = _normalize_row(row)
        assert out['date'] == '2026-04-01'
        assert out['clock_in'] == '09:00:00'
        assert out['clock_out'] == '18:00:00'
        assert out['lunch_minutes'] == 60
        assert out['memo'] == '메모'

    def test_optional_clock_out(self):
        row = {'date': '2026-04-01', 'clock_in': '09:00', 'clock_out': '',
               'lunch_minutes': '0', 'memo': ''}
        out = _normalize_row(row)
        assert out['clock_out'] is None

    def test_invalid_date(self):
        row = {'date': 'not-a-date', 'clock_in': '09:00', 'clock_out': '',
               'lunch_minutes': '0', 'memo': ''}
        with pytest.raises(ValueError):
            _normalize_row(row)

    def test_negative_lunch_minutes(self):
        row = {'date': '2026-04-01', 'clock_in': '09:00', 'clock_out': '',
               'lunch_minutes': '-30', 'memo': ''}
        with pytest.raises(ValueError):
            _normalize_row(row)


class TestParseCsv:
    def _write(self, content: str) -> str:
        f = tempfile.NamedTemporaryFile('w', encoding='utf-8',
                                          delete=False, suffix='.csv', newline='')
        f.write(content)
        f.close()
        return f.name

    def test_valid_csv(self):
        path = self._write(
            "date,clock_in,clock_out,lunch_minutes,memo\n"
            "2026-04-01,09:00,18:00,60,첫째날\n"
            "2026-04-02,09:30:00,17:30:00,30,단축\n"
        )
        try:
            rows = parse_csv(path)
            assert len(rows) == 2
            assert rows[0]['lunch_minutes'] == 60
            assert rows[1]['memo'] == '단축'
        finally:
            os.remove(path)

    def test_utf8_bom(self):
        # 엑셀 저장본 호환
        path = self._write('\ufeff' +
            "date,clock_in,clock_out,lunch_minutes,memo\n"
            "2026-04-01,09:00,18:00,60,첫째날\n"
        )
        try:
            rows = parse_csv(path)
            assert len(rows) == 1
        finally:
            os.remove(path)

    def test_missing_required_header(self):
        path = self._write("date,memo\n2026-04-01,foo\n")
        try:
            with pytest.raises(ValueError) as exc:
                parse_csv(path)
            assert 'clock_in' in str(exc.value)
        finally:
            os.remove(path)

    def test_file_not_found(self):
        with pytest.raises(FileNotFoundError):
            parse_csv('/nonexistent/file.csv')

    def test_line_number_in_error(self):
        path = self._write(
            "date,clock_in,clock_out,lunch_minutes,memo\n"
            "2026-04-01,09:00,18:00,60,ok\n"
            "bad-date,09:00,18:00,60,broken\n"
        )
        try:
            with pytest.raises(ValueError) as exc:
                parse_csv(path)
            assert '줄 3' in str(exc.value)
        finally:
            os.remove(path)


class TestImportRecords:
    def _db(self):
        p = tempfile.NamedTemporaryFile(delete=False, suffix='.db')
        p.close()
        db = Database(p.name)
        db.save_settings({
            'work_minutes': '480',
            'lunch_duration_minutes': '60',
            'dinner_duration_minutes': '60',
        })
        return db, p.name

    def test_overwrite_clears_overtime_usage(self):
        """CSV 덮어쓰기 시 overtime_usage도 삭제되어 잔액이 일관성을 유지해야 함."""
        db, path = self._db()
        try:
            date_str = '2026-04-01'
            # 기존 기록 + 연장근무 사용 기록 생성
            wid = db.add_work_record(date_str, '09:00:00')
            db.update_clock_out(date_str, '20:00:00', 11.0, 120, 120)
            db.add_overtime_earned(wid, 120, date_str)
            db.add_overtime_usage(wid, 30, date_str, '테스트')

            # 덮어쓰기 전 잔액
            balance_before = db.get_total_overtime_balance()
            assert balance_before == 90  # 120 적립 - 30 사용

            rows = [{
                'date': date_str,
                'clock_in': '09:00:00',
                'clock_out': '18:00:00',
                'lunch_minutes': 60,
                'dinner_minutes': 0,
                'memo': '',
            }]
            import_records(db, rows, on_conflict='overwrite')

            # 덮어쓰기 후 연장근무 사용 기록은 삭제되어야 함
            with db._conn() as conn:
                cur = conn.cursor()
                cur.execute("SELECT COUNT(*) FROM overtime_usage WHERE date = ?", (date_str,))
                assert cur.fetchone()[0] == 0
            balance_after = db.get_total_overtime_balance()
            assert balance_after == 0  # 새 기록은 연장근무 없음
        finally:
            try:
                os.remove(path)
            except OSError:
                pass
