# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========

from typing import Any

from fastapi import APIRouter, HTTPException, Query, Request

from mirage import Workspace
from mirage.server.clone import clone_workspace_with_override
from mirage.server.summary import make_detail
from mirage.server.version.api import (branch, checkout, commit_state,
                                       diff_live_vs_ref, read_version,
                                       resolve_ref, status_state, version_diff,
                                       version_log)
from mirage.server.version.errors import HeadMovedError, NoSuchBranchError
from mirage.server.version.state_tree import to_state
from mirage.server.version.store import VersionStore
from mirage.workspace.snapshot import to_state_dict

from mirage.server.schemas import (  # isort: skip
    BranchRequest, BranchResponse, CheckoutRequest, CloneRequest,
    CommitRequest, CommitResponse, DiffResponse, VersionLogItem,
    WorkspaceDetail)

router = APIRouter(prefix="/v1")


async def _state_of(ws: Workspace) -> dict[str, Any]:
    return await to_state_dict(ws)


@router.post("/workspaces/{workspace_id}/commit",
             response_model=CommitResponse)
async def commit_version(workspace_id: str, req: CommitRequest,
                         request: Request) -> CommitResponse:
    registry = request.app.state.registry
    if workspace_id not in registry:
        raise HTTPException(status_code=404, detail="workspace not found")
    entry = registry.get(workspace_id)
    state = await entry.runner.call(_state_of(entry.runner.ws))
    store = await VersionStore.open(request.app.state.version_backend,
                                    workspace_id)
    try:
        version = await commit_state(store, state, req.branch, req.message)
    except HeadMovedError as e:
        raise HTTPException(status_code=409, detail=str(e))
    except NoSuchBranchError as e:
        raise HTTPException(status_code=404, detail=str(e))
    return CommitResponse(version=version.decode(), branch=req.branch)


@router.get("/workspaces/{workspace_id}/versions",
            response_model=list[VersionLogItem])
async def list_versions(
    workspace_id: str, request: Request, branch: str = Query("main")
) -> list[VersionLogItem]:  # noqa: E125
    store = await VersionStore.open(request.app.state.version_backend,
                                    workspace_id)
    if branch not in await store.branches():
        return []
    entries = await version_log(store, branch)
    return [VersionLogItem(id=e["id"], message=e["message"]) for e in entries]


@router.post("/workspaces/{workspace_id}/branch",
             response_model=BranchResponse,
             status_code=201)
async def create_branch(workspace_id: str, req: BranchRequest,
                        request: Request) -> BranchResponse:
    store = await VersionStore.open(request.app.state.version_backend,
                                    workspace_id)
    if req.name in await store.branches():
        raise HTTPException(status_code=409,
                            detail=f"branch already exists: {req.name!r}")
    try:
        await branch(store, req.name, req.from_branch)
    except KeyError:
        raise HTTPException(status_code=404,
                            detail=f"no such branch: {req.from_branch!r}")
    head = await store.head(req.name)
    return BranchResponse(branch=req.name, version=head.decode())


@router.get("/workspaces/{workspace_id}/diff", response_model=DiffResponse)
async def diff_versions(
        workspace_id: str,
        request: Request,
        a: str | None = Query(None),
        b: str | None = Query(None),
        branch: str = Query("main"),
) -> DiffResponse:  # noqa: E125
    store = await VersionStore.open(request.app.state.version_backend,
                                    workspace_id)
    if a is not None and b is not None:
        try:
            changes = await version_diff(store, await resolve_ref(store, a),
                                         await resolve_ref(store, b))
        except KeyError:
            raise HTTPException(status_code=404, detail="version not found")
        return DiffResponse(**changes)

    registry = request.app.state.registry
    if workspace_id not in registry:
        raise HTTPException(status_code=404, detail="workspace not found")
    entry = registry.get(workspace_id)
    state = await entry.runner.call(_state_of(entry.runner.ws))
    try:
        if a is not None:
            changes = await diff_live_vs_ref(store, state, a)
        else:
            changes = await status_state(store, state, branch)
    except KeyError:
        raise HTTPException(status_code=404, detail="version not found")
    return DiffResponse(**changes)


@router.post("/workspaces/{workspace_id}/checkout",
             response_model=WorkspaceDetail)
async def checkout_version(workspace_id: str, req: CheckoutRequest,
                           request: Request) -> WorkspaceDetail:
    registry = request.app.state.registry
    if workspace_id not in registry:
        raise HTTPException(status_code=404, detail="workspace not found")
    entry = registry.get(workspace_id)
    store = await VersionStore.open(request.app.state.version_backend,
                                    workspace_id)
    try:
        await entry.runner.call(checkout(store, entry.runner.ws, req.ref))
    except KeyError:
        raise HTTPException(status_code=404,
                            detail=f"version not found: {req.ref}")
    return await make_detail(entry)


@router.post("/workspaces/clone",
             response_model=WorkspaceDetail,
             status_code=201)
async def clone_workspace_version(req: CloneRequest,
                                  request: Request) -> WorkspaceDetail:
    registry = request.app.state.registry
    if req.id is not None and req.id in registry:
        raise HTTPException(status_code=409,
                            detail=f"workspace id already exists: {req.id!r}")
    if req.at is not None:
        store = await VersionStore.open(request.app.state.version_backend,
                                        req.source_id)
        version = await resolve_ref(store, req.at)
        try:
            entries, meta = await read_version(store, version)
        except KeyError:
            raise HTTPException(status_code=404,
                                detail=f"version not found: {req.at}")
        ws = await Workspace.from_state(to_state(entries, meta))
    else:
        if req.source_id not in registry:
            raise HTTPException(status_code=404, detail="workspace not found")
        src = registry.get(req.source_id)
        ws = await src.runner.call(
            clone_workspace_with_override(src.runner.ws, None))
    try:
        entry = registry.add(ws, workspace_id=req.id)
    except ValueError as e:
        raise HTTPException(status_code=409, detail=str(e))
    return await make_detail(entry)
