import json
import os
from datetime import date, datetime
from unittest.mock import MagicMock, patch

import requests
from PIL import Image as PILImage

from app import db
from src.models import Article, Image, Issue
import config


def test_regenerate_passes_ordered_image_paths_to_generate_cover(app, client, db, tmp_path):
    """First image per article, in pub_date order, passed to generate_cover."""
    os.makedirs(tmp_path, exist_ok=True)
    img1 = str(tmp_path / "a1.jpg")
    img2 = str(tmp_path / "a2.jpg")
    for p in (img1, img2):
        PILImage.new("RGB", (100, 100), color="blue").save(p, format="JPEG")

    with app.app_context():
        a1 = Article(
            guid="g1",
            title="Earlier Article",
            author="A",
            pub_date=datetime(2026, 4, 6, 10, 0),
            categories=json.dumps(["Government"]),
            link="http://example.com/1",
            content_html="<p>x</p>",
        )
        a2 = Article(
            guid="g2",
            title="Later Article",
            author="B",
            pub_date=datetime(2026, 4, 7, 10, 0),
            categories=json.dumps(["Culture"]),
            link="http://example.com/2",
            content_html="<p>y</p>",
        )
        db.session.add_all([a1, a2])
        db.session.flush()
        db.session.add_all(
            [
                Image(
                    article_id=a1.id,
                    original_url="https://example.com/1.jpg",
                    local_path=img1,
                    width=100,
                    height=100,
                ),
                Image(
                    article_id=a2.id,
                    original_url="https://example.com/2.jpg",
                    local_path=img2,
                    width=100,
                    height=100,
                ),
            ]
        )
        id1, id2 = a1.id, a2.id
        issue = Issue(
            week_start=date(2026, 4, 6),
            week_end=date(2026, 4, 12),
            cover_method="mosaic",
            cover_path=str(tmp_path / "old-cover.jpg"),
            epub_path=str(tmp_path / "old.epub"),
            article_ids=json.dumps([id2, id1]),
            status="published",
        )
        db.session.add(issue)
        db.session.commit()
        issue_id = issue.id

    mock_cover = MagicMock(return_value=str(tmp_path / "new-cover.jpg"))
    mock_epub = MagicMock(return_value=str(tmp_path / "new.epub"))

    with patch("src.routes.issues.generate_cover", mock_cover), patch(
        "src.routes.issues.build_epub", mock_epub
    ):
        resp = client.post(f"/issues/{issue_id}/regenerate")

    assert resp.status_code in (302, 303)
    mock_cover.assert_called_once()
    args = mock_cover.call_args[0]
    assert args[0] == "mosaic"
    assert args[4] == ["Earlier Article", "Later Article"]
    assert args[5] == [img1, img2]


def test_regenerate_maps_ai_cover_method_to_mosaic(app, client, db, tmp_path):
    """Legacy ai issues use mosaic generation and persist cover_method=mosaic."""
    os.makedirs(tmp_path, exist_ok=True)
    img1 = str(tmp_path / "a1.jpg")
    PILImage.new("RGB", (100, 100), color="blue").save(img1, format="JPEG")

    with app.app_context():
        a1 = Article(
            guid="g-ai",
            title="Some Article",
            author="A",
            pub_date=datetime(2026, 4, 6, 10, 0),
            categories=json.dumps(["Government"]),
            link="http://example.com/1",
            content_html="<p>x</p>",
        )
        db.session.add(a1)
        db.session.flush()
        db.session.add(
            Image(
                article_id=a1.id,
                original_url="https://example.com/1.jpg",
                local_path=img1,
                width=100,
                height=100,
            )
        )
        issue = Issue(
            week_start=date(2026, 4, 6),
            week_end=date(2026, 4, 12),
            cover_method="ai",
            cover_path=str(tmp_path / "old-cover.jpg"),
            epub_path=str(tmp_path / "old.epub"),
            article_ids=json.dumps([a1.id]),
            status="published",
        )
        db.session.add(issue)
        db.session.commit()
        issue_id = issue.id

    mock_cover = MagicMock(return_value=str(tmp_path / "new-cover.jpg"))
    mock_epub = MagicMock(return_value=str(tmp_path / "new.epub"))

    with patch("src.routes.issues.generate_cover", mock_cover), patch(
        "src.routes.issues.build_epub", mock_epub
    ):
        client.post(f"/issues/{issue_id}/regenerate")

    mock_cover.assert_called_once()
    assert mock_cover.call_args[0][0] == "mosaic"

    with app.app_context():
        updated = db.session.get(Issue, issue_id)
        assert updated.cover_method == "mosaic"


def test_push_issue_success(app, client, db, tmp_path):
    """Test successful push to Grimmory API."""
    os.makedirs(tmp_path, exist_ok=True)
    epub_path = tmp_path / "test.epub"
    epub_path.write_text("dummy epub content")

    with app.app_context():
        issue = Issue(
            week_start=date(2026, 4, 6),
            week_end=date(2026, 4, 12),
            cover_method="mosaic",
            cover_path=str(tmp_path / "cover.jpg"),
            epub_path=str(epub_path),
            article_ids=json.dumps([1]),
            status="published",
        )
        db.session.add(issue)
        db.session.commit()
        issue_id = issue.id

    mock_login_resp = MagicMock()
    mock_login_resp.status_code = 200
    mock_login_resp.json.return_value = {"token": "test_token"}

    mock_libraries_resp = MagicMock()
    mock_libraries_resp.status_code = 200
    mock_libraries_resp.json.return_value = [{"id": 123, "name": "lib123", "paths": [{"id": 456}]}]

    mock_upload_resp = MagicMock()
    mock_upload_resp.status_code = 200

    def mock_post(url, *args, **kwargs):
        if url.endswith("/api/v1/auth/login"):
            return mock_login_resp
        elif url.endswith("/api/v1/files/upload"):
            return mock_upload_resp
        return MagicMock(status_code=404)

    def mock_get(url, *args, **kwargs):
        if url.endswith("/api/v1/libraries"):
            return mock_libraries_resp
        return MagicMock(status_code=404)

    with patch("src.routes.issues.requests.post", side_effect=mock_post) as mock_post_call, \
         patch("src.routes.issues.requests.get", side_effect=mock_get) as mock_get_call:
        with patch.object(config, "GRIMMORY_URL", "http://grimmory.test"), \
             patch.object(config, "GRIMMORY_USERNAME", "admin"), \
             patch.object(config, "GRIMMORY_PASSWORD", "password"), \
             patch.object(config, "GRIMMORY_LIBRARY_ID", "lib123"):

            resp = client.post(f"/issues/{issue_id}/push")

            assert resp.status_code in (302, 303)
            assert mock_post_call.call_count == 2
            assert mock_get_call.call_count == 1

            # Check login call
            login_args, login_kwargs = mock_post_call.call_args_list[0]
            assert login_args[0] == "http://grimmory.test/api/v1/auth/login"
            assert login_kwargs["json"] == {"username": "admin", "password": "password"}

            # Check libraries call
            get_args, get_kwargs = mock_get_call.call_args_list[0]
            assert get_args[0] == "http://grimmory.test/api/v1/libraries"
            assert get_kwargs["headers"] == {"Authorization": "Bearer test_token"}

            # Check upload call
            upload_args, upload_kwargs = mock_post_call.call_args_list[1]
            assert upload_args[0] == "http://grimmory.test/api/v1/files/upload"
            assert upload_kwargs["headers"] == {"Authorization": "Bearer test_token"}
            assert upload_kwargs["data"] == {"libraryId": 123, "pathId": 456}
            assert "file" in upload_kwargs["files"]


def test_push_issue_missing_config(app, client, db, tmp_path):
    """Test push fails gracefully when config is missing."""
    with app.app_context():
        issue = Issue(
            week_start=date(2026, 4, 6),
            week_end=date(2026, 4, 12),
            cover_method="mosaic",
            cover_path=str(tmp_path / "cover.jpg"),
            epub_path=str(tmp_path / "test.epub"),
            article_ids=json.dumps([1]),
            status="published",
        )
        db.session.add(issue)
        db.session.commit()
        issue_id = issue.id

    with patch("src.routes.issues.requests.post") as mock_post:
        with patch.object(config, "GRIMMORY_URL", None), \
             patch.object(config, "GRIMMORY_USERNAME", None), \
             patch.object(config, "GRIMMORY_PASSWORD", None):
            
            resp = client.post(f"/issues/{issue_id}/push")
            
            assert resp.status_code in (302, 303)
            mock_post.assert_not_called()


def test_push_issue_missing_file(app, client, db, tmp_path):
    """Test push fails gracefully when epub file is missing."""
    with app.app_context():
        issue = Issue(
            week_start=date(2026, 4, 6),
            week_end=date(2026, 4, 12),
            cover_method="mosaic",
            cover_path=str(tmp_path / "cover.jpg"),
            epub_path="/nonexistent/path/test.epub",
            article_ids=json.dumps([1]),
            status="published",
        )
        db.session.add(issue)
        db.session.commit()
        issue_id = issue.id

    with patch("src.routes.issues.requests.post") as mock_post:
        with patch.object(config, "GRIMMORY_URL", "http://grimmory.test"), \
             patch.object(config, "GRIMMORY_USERNAME", "admin"), \
             patch.object(config, "GRIMMORY_PASSWORD", "password"):
            
            resp = client.post(f"/issues/{issue_id}/push")
            
            assert resp.status_code in (302, 303)
            mock_post.assert_not_called()


def test_push_issue_login_error(app, client, db, tmp_path):
    """Test push handles login errors gracefully."""
    os.makedirs(tmp_path, exist_ok=True)
    epub_path = tmp_path / "test.epub"
    epub_path.write_text("dummy epub content")

    with app.app_context():
        issue = Issue(
            week_start=date(2026, 4, 6),
            week_end=date(2026, 4, 12),
            cover_method="mosaic",
            cover_path=str(tmp_path / "cover.jpg"),
            epub_path=str(epub_path),
            article_ids=json.dumps([1]),
            status="published",
        )
        db.session.add(issue)
        db.session.commit()
        issue_id = issue.id

    mock_login_resp = MagicMock()
    mock_login_resp.status_code = 401
    mock_login_resp.text = "Unauthorized"

    with patch("src.routes.issues.requests.post", return_value=mock_login_resp) as mock_post_call:
        with patch.object(config, "GRIMMORY_URL", "http://grimmory.test"), \
             patch.object(config, "GRIMMORY_USERNAME", "admin"), \
             patch.object(config, "GRIMMORY_PASSWORD", "wrong"):
            
            resp = client.post(f"/issues/{issue_id}/push")
            
            assert resp.status_code in (302, 303)
            assert mock_post_call.call_count == 1
            
            login_args, _ = mock_post_call.call_args_list[0]
            assert login_args[0] == "http://grimmory.test/api/v1/auth/login"


def test_push_issue_api_error(app, client, db, tmp_path):
    """Test push handles API errors gracefully."""
    os.makedirs(tmp_path, exist_ok=True)
    epub_path = tmp_path / "test.epub"
    epub_path.write_text("dummy epub content")

    with app.app_context():
        issue = Issue(
            week_start=date(2026, 4, 6),
            week_end=date(2026, 4, 12),
            cover_method="mosaic",
            cover_path=str(tmp_path / "cover.jpg"),
            epub_path=str(epub_path),
            article_ids=json.dumps([1]),
            status="published",
        )
        db.session.add(issue)
        db.session.commit()
        issue_id = issue.id

    mock_login_resp = MagicMock()
    mock_login_resp.status_code = 200
    mock_login_resp.json.return_value = {"token": "test_token"}

    mock_upload_resp = MagicMock()
    mock_upload_resp.status_code = 500
    mock_upload_resp.text = "Internal Server Error"

    def mock_post(url, *args, **kwargs):
        if url.endswith("/api/v1/auth/login"):
            return mock_login_resp
        elif url.endswith("/api/v1/files/upload"):
            return mock_upload_resp
        return MagicMock(status_code=404)

    def mock_get(url, *args, **kwargs):
        return MagicMock(status_code=200, json=lambda: [{"id": 123, "name": "lib123", "paths": [{"id": 456}]}])

    with patch("src.routes.issues.requests.post", side_effect=mock_post) as mock_post_call, \
         patch("src.routes.issues.requests.get", side_effect=mock_get) as mock_get_call:
        with patch.object(config, "GRIMMORY_URL", "http://grimmory.test"), \
             patch.object(config, "GRIMMORY_USERNAME", "admin"), \
             patch.object(config, "GRIMMORY_PASSWORD", "password"), \
             patch.object(config, "GRIMMORY_LIBRARY_ID", "lib123"):

            resp = client.post(f"/issues/{issue_id}/push")

            assert resp.status_code in (302, 303)
            assert mock_post_call.call_count == 2
