# -*- coding: utf-8 -*-
"""Unit tests for Hold History API routes (two-phase query/view pattern)."""

import json
import unittest
from unittest.mock import patch

from mes_dashboard.app import create_app
import mes_dashboard.core.database as db


class TestHoldHistoryRoutesBase(unittest.TestCase):
    """Base class for Hold History route tests."""

    def setUp(self):
        db._ENGINE = None
        self.app = create_app('testing')
        self.app.config['TESTING'] = True
        self.client = self.app.test_client()


class TestHoldHistoryPageRoute(TestHoldHistoryRoutesBase):
    """Test GET /hold-history page route."""

    @patch('mes_dashboard.routes.hold_history_routes.os.path.exists', return_value=False)
    def test_hold_history_page_includes_vite_entry(self, _mock_exists):
        response = self.client.get('/hold-history', follow_redirects=False)
        self.assertEqual(response.status_code, 302)
        self.assertTrue(response.location.endswith('/portal-shell/hold-history'))

    @patch('mes_dashboard.routes.hold_history_routes.os.path.exists', return_value=False)
    def test_hold_history_page_redirects_without_admin(self, _mock_exists):
        response = self.client.get('/hold-history', follow_redirects=False)
        self.assertEqual(response.status_code, 302)
        self.assertTrue(response.location.endswith('/portal-shell/hold-history'))


class TestHoldHistoryQueryRoute(TestHoldHistoryRoutesBase):
    """Test POST /api/hold-history/query endpoint."""

    def test_query_missing_dates_returns_400(self):
        response = self.client.post(
            '/api/hold-history/query',
            json={'start_date': '2026-02-01'},
        )
        payload = json.loads(response.data)

        self.assertEqual(response.status_code, 400)
        self.assertFalse(payload['success'])

    def test_query_invalid_date_format_returns_400(self):
        response = self.client.post(
            '/api/hold-history/query',
            json={'start_date': '2026/02/01', 'end_date': '2026-02-07'},
        )
        payload = json.loads(response.data)

        self.assertEqual(response.status_code, 400)
        self.assertFalse(payload['success'])

    def test_query_end_before_start_returns_400(self):
        response = self.client.post(
            '/api/hold-history/query',
            json={'start_date': '2026-02-07', 'end_date': '2026-02-01'},
        )
        payload = json.loads(response.data)

        self.assertEqual(response.status_code, 400)
        self.assertFalse(payload['success'])

    def test_query_invalid_record_type_returns_400(self):
        response = self.client.post(
            '/api/hold-history/query',
            json={
                'start_date': '2026-02-01',
                'end_date': '2026-02-07',
                'record_type': 'invalid',
            },
        )
        payload = json.loads(response.data)

        self.assertEqual(response.status_code, 400)
        self.assertFalse(payload['success'])

    @patch('mes_dashboard.routes.hold_history_routes.execute_primary_query')
    def test_query_success(self, mock_exec):
        mock_exec.return_value = {
            'query_id': 'abc123',
            'trend': {'days': []},
            'reason_pareto': {'items': []},
            'duration': {'items': []},
            'list': {'items': [], 'pagination': {}},
        }

        response = self.client.post(
            '/api/hold-history/query',
            json={
                'start_date': '2026-02-01',
                'end_date': '2026-02-07',
                'hold_type': 'quality',
            },
        )
        payload = json.loads(response.data)

        self.assertEqual(response.status_code, 200)
        self.assertTrue(payload['success'])
        self.assertIn('query_id', payload['data'])

    @patch('mes_dashboard.routes.hold_history_routes.execute_primary_query')
    def test_query_passes_params(self, mock_exec):
        mock_exec.return_value = {'query_id': 'x', 'trend': {}, 'reason_pareto': {}, 'duration': {}, 'list': {}}

        self.client.post(
            '/api/hold-history/query',
            json={
                'start_date': '2026-02-01',
                'end_date': '2026-02-07',
                'hold_type': 'non-quality',
                'record_type': 'on_hold',
            },
        )

        mock_exec.assert_called_once_with(
            start_date='2026-02-01',
            end_date='2026-02-07',
            hold_type='non-quality',
            record_type='on_hold',
        )

    @patch('mes_dashboard.routes.hold_history_routes.execute_primary_query')
    @patch('mes_dashboard.core.rate_limit.check_and_record', return_value=(True, 8))
    def test_query_rate_limited_returns_429(self, _mock_limit, mock_exec):
        response = self.client.post(
            '/api/hold-history/query',
            json={'start_date': '2026-02-01', 'end_date': '2026-02-07'},
        )
        payload = json.loads(response.data)

        self.assertEqual(response.status_code, 429)
        self.assertEqual(payload['error']['code'], 'TOO_MANY_REQUESTS')
        self.assertEqual(response.headers.get('Retry-After'), '8')
        mock_exec.assert_not_called()


class TestHoldHistoryViewRoute(TestHoldHistoryRoutesBase):
    """Test GET /api/hold-history/view endpoint."""

    def test_view_missing_query_id_returns_400(self):
        response = self.client.get('/api/hold-history/view')
        payload = json.loads(response.data)

        self.assertEqual(response.status_code, 400)
        self.assertFalse(payload['success'])
        self.assertIn('query_id', payload['error'])

    def test_view_invalid_record_type_returns_400(self):
        response = self.client.get(
            '/api/hold-history/view?query_id=abc123&record_type=bogus'
        )
        payload = json.loads(response.data)

        self.assertEqual(response.status_code, 400)
        self.assertFalse(payload['success'])

    def test_view_invalid_duration_range_returns_400(self):
        response = self.client.get(
            '/api/hold-history/view?query_id=abc123&duration_range=invalid'
        )
        payload = json.loads(response.data)

        self.assertEqual(response.status_code, 400)
        self.assertFalse(payload['success'])

    @patch('mes_dashboard.routes.hold_history_routes.apply_view')
    def test_view_cache_expired_returns_410(self, mock_view):
        mock_view.return_value = None

        response = self.client.get('/api/hold-history/view?query_id=abc123')
        payload = json.loads(response.data)

        self.assertEqual(response.status_code, 410)
        self.assertFalse(payload['success'])
        self.assertEqual(payload['error'], 'cache_expired')

    @patch('mes_dashboard.routes.hold_history_routes.apply_view')
    def test_view_success(self, mock_view):
        mock_view.return_value = {
            'trend': {'days': []},
            'reason_pareto': {'items': []},
            'duration': {'items': []},
            'list': {'items': [], 'pagination': {}},
        }

        response = self.client.get(
            '/api/hold-history/view?query_id=abc123&hold_type=non-quality&reason=品質確認&page=2&per_page=20'
        )
        payload = json.loads(response.data)

        self.assertEqual(response.status_code, 200)
        self.assertTrue(payload['success'])

        mock_view.assert_called_once_with(
            query_id='abc123',
            hold_type='non-quality',
            reason='品質確認',
            record_type='new',
            duration_range=None,
            page=2,
            per_page=20,
        )

    @patch('mes_dashboard.routes.hold_history_routes.apply_view')
    def test_view_caps_per_page(self, mock_view):
        mock_view.return_value = {
            'trend': {'days': []},
            'reason_pareto': {'items': []},
            'duration': {'items': []},
            'list': {'items': [], 'pagination': {}},
        }

        self.client.get(
            '/api/hold-history/view?query_id=abc123&page=0&per_page=500'
        )

        call_kwargs = mock_view.call_args[1]
        self.assertEqual(call_kwargs['page'], 1)
        self.assertEqual(call_kwargs['per_page'], 200)

    @patch('mes_dashboard.routes.hold_history_routes.apply_view')
    def test_view_passes_duration_range(self, mock_view):
        mock_view.return_value = {
            'trend': {'days': []},
            'reason_pareto': {'items': []},
            'duration': {'items': []},
            'list': {'items': [], 'pagination': {}},
        }

        self.client.get(
            '/api/hold-history/view?query_id=abc123&duration_range=<4h'
        )

        call_kwargs = mock_view.call_args[1]
        self.assertEqual(call_kwargs['duration_range'], '<4h')

    @patch('mes_dashboard.routes.hold_history_routes.apply_view')
    @patch('mes_dashboard.core.rate_limit.check_and_record', return_value=(True, 5))
    def test_view_rate_limited_returns_429(self, _mock_limit, mock_view):
        response = self.client.get('/api/hold-history/view?query_id=abc123')
        payload = json.loads(response.data)

        self.assertEqual(response.status_code, 429)
        self.assertEqual(payload['error']['code'], 'TOO_MANY_REQUESTS')
        self.assertEqual(response.headers.get('Retry-After'), '5')
        mock_view.assert_not_called()
