from __future__ import annotations

import os

from argparse import Namespace
from pathlib import Path

from flask import Blueprint, request, jsonify

from apps.aroflo_connector_app.ui_automation_zones.core.config import build_config
from apps.aroflo_connector_app.ui_automation_zones.core.artifacts import create_run_dir, shot
from apps.aroflo_connector_app.ui_automation_zones.core.runtime import SESSION_POOL
from apps.aroflo_connector_app.ui_automation_zones.auth.detect import is_mfa_screen, is_authenticated
from apps.aroflo_connector_app.ui_automation_zones.zones.timesheets.flows import nav as ts_nav
from apps.aroflo_connector_app.ui_automation_zones.zones.timesheets.flows import select_date as ts_date
from apps.aroflo_connector_app.ui_automation_zones.zones.timesheets.flows import select_user as ts_user
from apps.aroflo_connector_app.ui_automation_zones.zones.timesheets.flows import create as ts_create
from apps.aroflo_connector_app.ui_automation_zones.zones.timesheets.flows import delete as ts_delete
from apps.aroflo_connector_app.ui_automation_zones.zones.timesheets.flows.create import NewRowSpec
from apps.aroflo_connector_app.ui_automation_zones.zones.timesheets.flows.delete import DeleteRule
from apps.aroflo_connector_app.ui_automation_zones.zones.users.flows import nav as users_nav
from apps.aroflo_connector_app.ui_automation_zones.zones.users.flows import select_user as users_select
from apps.aroflo_connector_app.ui_automation_zones.zones.users.flows import upload_docs as users_upload
from apps.aroflo_connector_app.ui_automation_zones.zones.users.flows.upload_docs import DocSpec
from apps.aroflo_connector_app.ui_automation_zones.common.siteadministration.settings.timesheets import overheads as sa_overheads

from apps.aroflo_connector_app.ui_automation_zones.services.session_service import (
    get_status,
    connect_session,
    submit_mfa_code,
    close_session,
)

ui_worker_bp = Blueprint("ui_automation_worker", __name__, url_prefix="/ui")


def _tenant_from(data: dict | None) -> str:
    data = data or {}
    tenant_id = (data.get("tenant_id") or data.get("tenant-id") or "").strip()
    return tenant_id or "<default>"


def _is_allowed_tenant(tenant_id: str) -> bool:
    allowed = (os.getenv("AROFLO_UI_ALLOWED_TENANT_ID") or "").strip()
    if not allowed:
        # default abierto si no se configura
        return True
    if allowed == "<default>":
        return tenant_id == "<default>"
    return tenant_id == allowed


def _deny_if_not_allowed(tenant_id: str):
    if not _is_allowed_tenant(tenant_id):
        return jsonify({"status": "denied", "message": "tenant_id not allowed"}), 403
    return None


def _build_cfg_for_tenant(tenant_id: str):
    args = Namespace(
        base_url=None,
        username=None,
        password=None,
        tenant_id=tenant_id,
        reuse_session=True,
        keep_open=True,
        state_file=None,
        artifacts_dir=None,
        headless=None,
        slow_mo_ms=None,
        step=False,
        pause_on_mfa=False,
        mfa_code="",

        user_email="",
        doc=[],
        file=[],
        comment=[],
        filter=[],

        timesheet_date="",
        timesheet_bu="",
        timesheet_user_id="",
        timesheet_user_name="",
        row=[],
        delete_all=False,
        include_protected=False,
    )
    return build_config(args)


def _get_runtime_or_error(tenant_id: str):
    rt = SESSION_POOL._sessions.get(tenant_id)
    if not rt:
        return None, (jsonify({"state": "OFFLINE", "tenant_id": tenant_id}), 409)
    try:
        if is_mfa_screen(rt.page):
            return None, (jsonify({"state": "MFA_REQUIRED", "tenant_id": tenant_id}), 409)
        if not is_authenticated(rt.page):
            return None, (jsonify({"state": "OFFLINE", "tenant_id": tenant_id}), 409)
    except Exception:
        return None, (jsonify({"state": "UNKNOWN", "tenant_id": tenant_id}), 409)
    return rt, None


@ui_worker_bp.get("/session/status")
def session_status():
    tenant_id = _tenant_from(request.args)
    denied = _deny_if_not_allowed(tenant_id)
    if denied:
        return denied
    return jsonify(get_status(tenant_id=tenant_id))


@ui_worker_bp.post("/session/connect")
def session_connect():
    data = request.get_json(silent=True) or {}
    tenant_id = _tenant_from(data)
    denied = _deny_if_not_allowed(tenant_id)
    if denied:
        return denied
    out = connect_session(tenant_id=tenant_id)
    return jsonify(out)


@ui_worker_bp.post("/session/mfa")
def session_mfa():
    data = request.get_json(silent=True) or {}
    tenant_id = _tenant_from(data)
    denied = _deny_if_not_allowed(tenant_id)
    if denied:
        return denied
    code = (data.get("code") or "").strip()
    if not code:
        return jsonify({"status": "error", "message": "Missing MFA code"}), 400
    out = submit_mfa_code(tenant_id=tenant_id, code=code)
    return jsonify(out)


@ui_worker_bp.post("/session/close")
def session_close():
    data = request.get_json(silent=True) or {}
    tenant_id = _tenant_from(data)
    denied = _deny_if_not_allowed(tenant_id)
    if denied:
        return denied
    out = close_session(tenant_id=tenant_id)
    return jsonify(out)


@ui_worker_bp.post("/timesheets/create")
def timesheets_create():
    data = request.get_json(silent=True) or {}
    tenant_id = _tenant_from(data)
    denied = _deny_if_not_allowed(tenant_id)
    if denied:
        return denied

    rt, err = _get_runtime_or_error(tenant_id)
    if err:
        return err

    cfg = _build_cfg_for_tenant(tenant_id)
    run_dir = create_run_dir(cfg, "worker-timesheets-create")

    cfg = cfg.__class__(
        **{**cfg.__dict__,
           "timesheet_bu": (data.get("timesheet_bu") or data.get("timesheet-bu") or "").strip(),
           "timesheet_date": (data.get("timesheet_date") or data.get("timesheet-date") or "").strip(),
           "timesheet_user_id": (data.get("timesheet_user_id") or data.get("timesheet-user-id") or "").strip(),
           "timesheet_user_name": (data.get("timesheet_user_name") or data.get("timesheet-user-name") or "").strip(),
        }
    )

    rows_raw = data.get("rows") or data.get("row") or []
    rows = None
    if rows_raw:
        parsed = []
        for spec in rows_raw:
            if isinstance(spec, dict):
                parsed.append(spec)
                continue
            parts = [p.strip() for p in str(spec).split(";") if p.strip()]
            out = {}
            for part in parts:
                if "=" not in part:
                    return jsonify({"status": "error", "message": f"Invalid row token: {part!r}"}), 400
                k, v = part.split("=", 1)
                out[k.strip().lower()] = v.strip()
            if not out.get("hours") or not out.get("overhead"):
                return jsonify({"status": "error", "message": f"--row missing hours/overhead: {spec}"}), 400
            out.setdefault("worktype", "NT")
            out.setdefault("tracking", "ADMIN")
            out.setdefault("note", "")
            parsed.append(out)
        rows = [
            NewRowSpec(
                hours=r["hours"],
                overhead=r["overhead"],
                note=(r.get("note") or None),
                worktype_label=r.get("worktype", "NT"),
                tracking_label=r.get("tracking", "ADMIN"),
            )
            for r in parsed
        ]

    lock = SESSION_POOL.tenant_lock(tenant_id)
    with lock:
        try:
            ts_nav.run(rt.page, cfg, run_dir)
            if cfg.timesheet_user_id or cfg.timesheet_user_name:
                ts_user.run(
                    rt.page,
                    cfg,
                    run_dir,
                    user_id=cfg.timesheet_user_id,
                    user_name=cfg.timesheet_user_name,
                )
            if cfg.timesheet_date:
                ts_date.run(rt.page, cfg, run_dir, target_date=cfg.timesheet_date)
            ts_create.run(rt.page, cfg, run_dir, rows=rows)
            shot(rt.page, run_dir, "ok")
            rt.save_state()
            rt.touch()
            return jsonify({"status": "ok", "tenant_id": tenant_id})
        except Exception as e:
            try:
                shot(rt.page, run_dir, "error")
            except Exception:
                pass
            return jsonify({"status": "error", "message": str(e), "tenant_id": tenant_id}), 500


@ui_worker_bp.post("/timesheets/delete")
def timesheets_delete():
    data = request.get_json(silent=True) or {}
    tenant_id = _tenant_from(data)
    denied = _deny_if_not_allowed(tenant_id)
    if denied:
        return denied

    rt, err = _get_runtime_or_error(tenant_id)
    if err:
        return err

    cfg = _build_cfg_for_tenant(tenant_id)
    run_dir = create_run_dir(cfg, "worker-timesheets-delete")

    cfg = cfg.__class__(
        **{**cfg.__dict__,
           "timesheet_bu": (data.get("timesheet_bu") or data.get("timesheet-bu") or "").strip(),
           "timesheet_date": (data.get("timesheet_date") or data.get("timesheet-date") or "").strip(),
           "timesheet_user_id": (data.get("timesheet_user_id") or data.get("timesheet-user-id") or "").strip(),
           "timesheet_user_name": (data.get("timesheet_user_name") or data.get("timesheet-user-name") or "").strip(),
           "delete_all": bool(data.get("delete_all", False)),
           "include_protected": bool(data.get("include_protected", False)),
        }
    )

    rule = DeleteRule(
        overheads_to_delete=[
            "Admin Duties",
            "Admin Duties - Telecommunications",
        ],
        protected_overheads=[
            "Lunch Break - Unpaid",
        ],
        match_mode="exact",
        delete_all=cfg.delete_all,
        include_protected=cfg.include_protected,
    )

    lock = SESSION_POOL.tenant_lock(tenant_id)
    with lock:
        try:
            ts_nav.run(rt.page, cfg, run_dir)
            if cfg.timesheet_user_id or cfg.timesheet_user_name:
                ts_user.run(
                    rt.page,
                    cfg,
                    run_dir,
                    user_id=cfg.timesheet_user_id,
                    user_name=cfg.timesheet_user_name,
                )
            if cfg.timesheet_date:
                ts_date.run(rt.page, cfg, run_dir, target_date=cfg.timesheet_date)
            ts_delete.run(rt.page, cfg, run_dir, rule=rule)
            shot(rt.page, run_dir, "ok")
            rt.save_state()
            rt.touch()
            return jsonify({"status": "ok", "tenant_id": tenant_id})
        except Exception as e:
            try:
                shot(rt.page, run_dir, "error")
            except Exception:
                pass
            return jsonify({"status": "error", "message": str(e), "tenant_id": tenant_id}), 500


@ui_worker_bp.post("/users/upload-docs")
def users_upload_docs():
    data = request.get_json(silent=True) or {}
    tenant_id = _tenant_from(data)
    denied = _deny_if_not_allowed(tenant_id)
    if denied:
        return denied

    rt, err = _get_runtime_or_error(tenant_id)
    if err:
        return err

    cfg = _build_cfg_for_tenant(tenant_id)
    run_dir = create_run_dir(cfg, "worker-users-upload-docs")

    cfg = cfg.__class__(
        **{**cfg.__dict__,
           "timesheet_bu": (data.get("timesheet_bu") or data.get("timesheet-bu") or "").strip(),
           "user_email": (data.get("user_email") or data.get("user-email") or "").strip(),
        }
    )

    docs_raw = data.get("docs") or []
    if not isinstance(docs_raw, list) or not docs_raw:
        return jsonify({"status": "error", "message": "docs is required"}), 400

    docs: list[DocSpec] = []
    for item in docs_raw:
        if isinstance(item, dict):
            f = (item.get("file") or "").strip()
            if not f:
                return jsonify({"status": "error", "message": "doc missing file"}), 400
            docs.append(DocSpec(file=f, comment=item.get("comment", ""), filter=item.get("filter", "")))
            continue
        parts = [p.strip() for p in str(item).split(";") if p.strip()]
        kv = {}
        for part in parts:
            if "=" not in part:
                return jsonify({"status": "error", "message": f"Invalid doc token: {part!r}"}), 400
            k, v = part.split("=", 1)
            kv[k.strip().lower()] = v.strip()
        if not kv.get("file"):
            return jsonify({"status": "error", "message": f"--doc missing file= in {item!r}"}), 400
        docs.append(DocSpec(file=kv["file"], comment=kv.get("comment", ""), filter=kv.get("filter", "")))

    lock = SESSION_POOL.tenant_lock(tenant_id)
    with lock:
        try:
            users_nav.run(rt.page, cfg, run_dir, select_user=False)
            result = users_select.select_user_by_email(rt.page, cfg.user_email, run_dir=run_dir)
            if not result.found:
                return jsonify({"status": "error", "message": "user not found", "tenant_id": tenant_id}), 404
            users_upload.users_upload_documents(rt.page, docs=docs, cfg=cfg, run_dir=run_dir, timeout_ms=45_000)
            shot(rt.page, run_dir, "ok")
            rt.save_state()
            rt.touch()
            return jsonify({"status": "ok", "tenant_id": tenant_id})
        except Exception as e:
            try:
                shot(rt.page, run_dir, "error")
            except Exception:
                pass
            return jsonify({"status": "error", "message": str(e), "tenant_id": tenant_id}), 500


@ui_worker_bp.post("/overheads/refresh")
def overheads_refresh():
    data = request.get_json(silent=True) or {}
    tenant_id = _tenant_from(data)
    denied = _deny_if_not_allowed(tenant_id)
    if denied:
        return denied

    rt, err = _get_runtime_or_error(tenant_id)
    if err:
        return err

    cfg = _build_cfg_for_tenant(tenant_id)
    run_dir = create_run_dir(cfg, "worker-overheads-refresh")

    lock = SESSION_POOL.tenant_lock(tenant_id)
    with lock:
        try:
            items = sa_overheads.run(rt.page, cfg, run_dir)
            shot(rt.page, run_dir, "ok")
            rt.save_state()
            rt.touch()
            return jsonify({"status": "ok", "tenant_id": tenant_id, "count": len(items or [])})
        except Exception as e:
            try:
                shot(rt.page, run_dir, "error")
            except Exception:
                pass
            return jsonify({"status": "error", "message": str(e), "tenant_id": tenant_id}), 500
