import asyncio
import logging
import os
import time
from typing import Dict, List, Tuple, Optional

from tabulate import tabulate

from dracs.display import (
    filter_list_results as _sync_filter_list_results,
    render_list_table,
    render_list_json,
    render_list_host_only,
)
from dracs.exceptions import (
    DatabaseError,
    DracsError,
    SNMPError,
    ValidationError,
)
from dracs.db import (
    System,
    get_session,
    db_initialize,
    query_by_service_tag,
    query_by_hostname,
    query_by_model,
    query_all_systems,
    upsert_system,
)
from dracs.snmp import get_snmp_value, build_idrac_hostname
from dracs.api import dell_api_warranty_date

logger = logging.getLogger(__name__)

debug_output = False


async def add_dell_warranty(
    service_tag: str,
    hostname: str,
    model: str,
    warranty: str,
    warranty_results: Optional[Dict] = None,
) -> None:
    idrac_host = build_idrac_hostname(hostname)
    community_string = os.getenv("SNMP_COMMUNITY", "public")
    BIOS_OID = "1.3.6.1.4.1.674.10892.5.4.300.50.1.8.1.1"
    IDRAC_FW_OID = "1.3.6.1.4.1.674.10892.5.1.1.8.0"

    bios_version = await get_snmp_value(idrac_host, community_string, BIOS_OID)
    idrac_version = await get_snmp_value(idrac_host, community_string, IDRAC_FW_OID)

    logger.info(f"Retrieved SNMP values - BIOS: {bios_version}, iDRAC: {idrac_version}")

    db_initialize(warranty)

    with get_session() as session:
        results = (
            session.query(System)
            .filter(System.svc_tag == service_tag, System.name == hostname)
            .all()
        )

    if debug_output:
        logger.debug(f"service_tag = {service_tag}")
        logger.debug(f"hostname = {hostname}")
        logger.debug(f"warranty = {warranty}")
        logger.debug(f"results = {results}")

    if len(results) > 1:
        raise DatabaseError("Multiple matching records found in database")

    if len(results) == 1:
        logger.info(f"Updating existing record for {service_tag}")
        upsert_system(
            warranty,
            service_tag,
            hostname,
            model,
            idrac_version,
            bios_version,
            results[0].exp_date,
            results[0].exp_epoch,
        )
        logger.info(f"Successfully updated record for {service_tag}")
    else:
        logger.info(
            f"Adding new record for {service_tag}, fetching warranty from Dell API"
        )
        if warranty_results is None:
            warranty_results = dell_api_warranty_date(service_tag)
        h_epoch, h_date = warranty_results[service_tag]

        if debug_output:
            logger.debug(
                f"Warranty result: svc_tag={service_tag}, exp_date={h_date}, "
                f"exp_epoch={h_epoch}"
            )

        upsert_system(
            warranty,
            service_tag,
            hostname,
            model,
            idrac_version,
            bios_version,
            h_date,
            h_epoch,
        )
        logger.info(f"Successfully added record for {service_tag}")


async def edit_dell_warranty(
    service_tag: Optional[str],
    hostname: Optional[str],
    model: Optional[str],
    idrac: bool,
    bios: bool,
    warranty: str,
) -> None:
    if service_tag:
        if debug_output:
            print(f"service_tag = {service_tag}")
    if hostname:
        if debug_output:
            print(f"hostname = {hostname}")
    if model:
        if debug_output:
            logger.debug(f"model = {model}")
    else:
        if not idrac and not bios:
            raise ValidationError(
                "Model parameter required for edit mode when not updating idrac or bios"
            )

    db_initialize(warranty)

    with get_session() as session:
        if service_tag:
            results = session.query(System).filter(System.svc_tag == service_tag).all()
        elif hostname:
            results = session.query(System).filter(System.name == hostname).all()
        else:
            results = []

    if debug_output:
        logger.debug(f"service_tag = {service_tag}")
        logger.debug(f"hostname = {hostname}")
        logger.debug(f"warranty = {warranty}")
        logger.debug(f"results = {results}")

    if len(results) > 1:
        raise DatabaseError("Multiple matching records found in database")

    if len(results) == 1:
        record = results[0]
        hostname = record.name
        idrac_host = build_idrac_hostname(hostname)
        community_string = os.getenv("SNMP_COMMUNITY", "public")
        BIOS_OID = "1.3.6.1.4.1.674.10892.5.4.300.50.1.8.1.1"
        IDRAC_FW_OID = "1.3.6.1.4.1.674.10892.5.1.1.8.0"

        if idrac:
            idrac_version = await get_snmp_value(
                idrac_host, community_string, IDRAC_FW_OID
            )
        else:
            idrac_version = record.idrac_version
        if bios:
            bios_version = await get_snmp_value(idrac_host, community_string, BIOS_OID)
        else:
            bios_version = record.bios_version
        if not model:
            model = record.model

        upsert_system(
            warranty,
            record.svc_tag,
            record.name,
            model,
            idrac_version,
            bios_version,
            record.exp_date,
            record.exp_epoch,
        )
        if debug_output:
            logger.info("Database updated successfully")
    else:
        raise DatabaseError("Record not found in database")


async def lookup_dell_warranty(
    service_tag: Optional[str],
    hostname: Optional[str],
    idrac: bool,
    bios: bool,
    full: bool,
    warranty: str,
) -> None:
    db_initialize(warranty)

    with get_session() as session:
        if service_tag:
            results = session.query(System).filter(System.svc_tag == service_tag).all()
        elif hostname:
            results = session.query(System).filter(System.name == hostname).all()
        else:
            results = []

    if len(results) == 0:
        raise DatabaseError("No matching records found in database")
    if len(results) > 1:
        raise DatabaseError("Multiple matching records found in database")

    record = results[0]
    result = {"hostname": record.name}
    if idrac or full:
        result["idrac_version"] = record.idrac_version
    if bios or full:
        result["bios_version"] = record.bios_version
    result["svc_tag"] = record.svc_tag
    if not idrac and not bios:
        result["model"] = record.model
        result["exp_date"] = record.exp_date
        result["exp_epoch"] = record.exp_epoch
    print(result)


async def filter_list_results(
    results: List[Tuple],
    bios_le: Optional[str],
    bios_lt: Optional[str],
    bios_ge: Optional[str],
    bios_gt: Optional[str],
    bios_eq: Optional[str],
    idrac_le: Optional[str],
    idrac_lt: Optional[str],
    idrac_ge: Optional[str],
    idrac_gt: Optional[str],
    idrac_eq: Optional[str],
) -> List[Tuple]:
    return _sync_filter_list_results(
        results,
        bios_le,
        bios_lt,
        bios_ge,
        bios_gt,
        bios_eq,
        idrac_le,
        idrac_lt,
        idrac_ge,
        idrac_gt,
        idrac_eq,
    )


async def list_dell_warranty(
    service_tag: Optional[str],
    hostname: Optional[str],
    model: Optional[str],
    regex: Optional[str],
    bios_le: Optional[str],
    bios_lt: Optional[str],
    bios_ge: Optional[str],
    bios_gt: Optional[str],
    bios_eq: Optional[str],
    idrac_le: Optional[str],
    idrac_lt: Optional[str],
    idrac_ge: Optional[str],
    idrac_gt: Optional[str],
    idrac_eq: Optional[str],
    expires_in: Optional[str],
    expired: bool,
    printjson: bool,
    host_only: bool,
    warranty: str,
) -> None:
    db_initialize(warranty)

    if service_tag and hostname:
        raise ValidationError(
            "Cannot specify both --svctag and --target; they are mutually exclusive"
        )
    if (hostname or service_tag) and (model or regex):
        raise ValidationError(
            "Cannot specify --model or --regex when using --svctag or --target"
        )

    with get_session() as session:
        query = session.query(System)

        if service_tag:
            query = query.filter(System.svc_tag == service_tag)
        elif hostname:
            query = query.filter(System.name == hostname)
        else:
            if model and regex:
                query = query.filter(System.name.like(regex), System.model == model)
            elif model:
                query = query.filter(System.model == model)
            elif regex:
                query = query.filter(System.name.like(regex))

        if expires_in:
            current_time = int(time.time())
            future_timestamp = current_time + (int(expires_in) * 86400)
            query = query.filter(
                System.exp_epoch > current_time,
                System.exp_epoch <= future_timestamp,
            )

        if expired:
            current_time = int(time.time())
            query = query.filter(System.exp_epoch < current_time)

        query = query.order_by(System.name)
        records = query.all()
        results = [r.to_tuple() for r in records]

    if (
        bios_le
        or bios_lt
        or bios_ge
        or bios_gt
        or bios_eq
        or idrac_le
        or idrac_lt
        or idrac_ge
        or idrac_gt
        or idrac_eq
    ):
        results = await filter_list_results(
            results,
            bios_le,
            bios_lt,
            bios_ge,
            bios_gt,
            bios_eq,
            idrac_le,
            idrac_lt,
            idrac_ge,
            idrac_gt,
            idrac_eq,
        )
    if host_only:
        render_list_host_only(results)
    elif printjson:
        render_list_json(results)
    else:
        render_list_table(results)


async def refresh_dell_warranty(
    service_tag: Optional[str],
    hostname: Optional[str],
    warranty: str,
    verbose: bool = False,
) -> None:
    db_initialize(warranty)

    if service_tag:
        results = query_by_service_tag(warranty, service_tag)
    elif hostname:
        results = query_by_hostname(warranty, hostname)
    else:
        raise ValidationError("Either service tag or hostname must be provided")

    if len(results) == 0:
        raise DatabaseError("No matching record found to refresh")
    if len(results) > 1:
        raise DatabaseError("Multiple matching records found in database")

    existing = results[0]
    svc_tag = existing[0]
    name = existing[1]
    old_model = existing[2]
    old_idrac_version = existing[3]
    old_bios_version = existing[4]
    old_exp_date = existing[5]

    if verbose:
        print(f"Refreshing {name} ... ", end="", flush=True)

    logger.debug(f"Refreshing data for {svc_tag} ({name})")

    idrac_host = build_idrac_hostname(name)
    community_string = os.getenv("SNMP_COMMUNITY", "public")
    BIOS_OID = "1.3.6.1.4.1.674.10892.5.4.300.50.1.8.1.1"
    IDRAC_FW_OID = "1.3.6.1.4.1.674.10892.5.1.1.8.0"
    MODEL_OID = ".1.3.6.1.4.1.674.10892.5.1.3.12.0"

    bios_version = await get_snmp_value(idrac_host, community_string, BIOS_OID)
    idrac_version = await get_snmp_value(idrac_host, community_string, IDRAC_FW_OID)
    model = await get_snmp_value(idrac_host, community_string, MODEL_OID)

    if model and model.startswith("PowerEdge "):
        model = model.replace("PowerEdge ", "")

    logger.debug(
        f"Updated SNMP values - Model: {model}, BIOS: {bios_version}, iDRAC: {idrac_version}"
    )

    warranty_results = dell_api_warranty_date(svc_tag)
    exp_epoch, exp_date = warranty_results[svc_tag]

    logger.debug(f"Updated warranty expiration: {exp_date}")

    upsert_system(
        warranty, svc_tag, name, model, idrac_version, bios_version, exp_date, exp_epoch
    )

    logger.debug(f"Successfully refreshed record for {svc_tag}")

    if verbose:
        print("done.")

    # Check for changes and report them
    from datetime import datetime

    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    if old_model != model:
        print(
            f"{timestamp} - dracs.cli - INFO - {name} updated: Model changed from {old_model} to {model}"
        )

    if old_idrac_version != idrac_version:
        print(
            f"{timestamp} - dracs.cli - INFO - {name} updated: Firmware changed from {old_idrac_version} to {idrac_version}"
        )

    if old_bios_version != bios_version:
        print(
            f"{timestamp} - dracs.cli - INFO - {name} updated: BIOS changed from {old_bios_version} to {bios_version}"
        )

    if old_exp_date != exp_date:
        print(
            f"{timestamp} - dracs.cli - INFO - {name} updated: Warranty Expiration changed from {old_exp_date} to {exp_date}"
        )


async def refresh_by_model(model: str, warranty: str, verbose: bool = False) -> None:
    db_initialize(warranty)
    results = query_by_model(warranty, model)

    if len(results) == 0:
        raise DatabaseError(f"No systems found with model {model}")

    success_count = 0
    for idx, result in enumerate(results, start=1):
        hostname = result[1]
        if verbose:
            print(f"Refreshing {hostname} ... ", end="", flush=True)
        try:
            await refresh_dell_warranty(None, hostname, warranty, verbose=False)
            if verbose:
                print("done.")
            success_count += 1
        except Exception as e:
            if verbose:
                print(f"failed: {e}")
            logger.error(f"Failed to refresh {hostname}: {e}")

        # Throttle after every 100 systems to avoid overwhelming Dell API
        if idx % 100 == 0 and idx < len(results):
            print("[ Waiting 30 seconds before proceeding ]")
            await asyncio.sleep(30)

    print(f"Summary: A total of {success_count} hosts refreshed")


async def refresh_all_systems(warranty: str, verbose: bool = False) -> None:
    db_initialize(warranty)
    results = query_all_systems(warranty)

    if len(results) == 0:
        raise DatabaseError("No systems found in database")

    success_count = 0
    for idx, result in enumerate(results, start=1):
        hostname = result[1]
        if verbose:
            print(f"Refreshing {hostname} ... ", end="", flush=True)
        try:
            await refresh_dell_warranty(None, hostname, warranty, verbose=False)
            if verbose:
                print("done.")
            success_count += 1
        except Exception as e:
            if verbose:
                print(f"failed: {e}")
            logger.error(f"Failed to refresh {hostname}: {e}")

        # Throttle after every 100 systems to avoid overwhelming Dell API
        if idx % 100 == 0 and idx < len(results):
            print("[ Waiting 30 seconds before proceeding ]")
            await asyncio.sleep(30)

    print(f"Summary: A total of {success_count} hosts refreshed")


async def discover_dell_system(hostname: str, warranty: str) -> Tuple[str, str]:
    logger.info(f"Discovering system information for {hostname}")

    idrac_host = build_idrac_hostname(hostname)
    community_string = os.getenv("SNMP_COMMUNITY", "public")

    SERVICE_TAG_OID = ".1.3.6.1.4.1.674.10892.5.1.3.2.0"
    MODEL_OID = ".1.3.6.1.4.1.674.10892.5.1.3.12.0"

    logger.info(f"Querying {idrac_host} for service tag and model")

    service_tag = await get_snmp_value(idrac_host, community_string, SERVICE_TAG_OID)
    if not service_tag:
        raise SNMPError(f"Failed to retrieve service tag from {idrac_host}")

    model = await get_snmp_value(idrac_host, community_string, MODEL_OID)
    if not model:
        raise SNMPError(f"Failed to retrieve model from {idrac_host}")

    if model.startswith("PowerEdge "):
        model = model.replace("PowerEdge ", "")

    logger.info(f"Discovered: Service Tag={service_tag}, Model={model}")

    return (service_tag, model)


async def _discover_single_host(hostname: str, warranty: str, auto_add: bool) -> dict:
    result = {"hostname": hostname, "status": "ok", "error": None}
    try:
        service_tag, model = await discover_dell_system(hostname, warranty)
        result["service_tag"] = service_tag
        result["model"] = model

        if auto_add:
            await add_dell_warranty(service_tag, hostname, model, warranty)
            result["added"] = True
        else:
            result["added"] = False
    except (SNMPError, DracsError) as e:
        result["status"] = "error"
        result["error"] = str(e)
        logger.error(f"Failed to discover {hostname}: {e}")

    return result


async def discover_dell_systems_batch(
    hosts: List[str], warranty: str, auto_add: bool, show_discovered: bool = False
) -> None:
    tasks = [_discover_single_host(h, warranty, False) for h in hosts]
    results = await asyncio.gather(*tasks)

    if auto_add:
        discovered = [r for r in results if r["status"] == "ok"]
        if discovered:
            tags = [r["service_tag"] for r in discovered]
            warranty_results = dell_api_warranty_date(tags)

            add_tasks = [
                add_dell_warranty(
                    r["service_tag"],
                    r["hostname"],
                    r["model"],
                    warranty,
                    warranty_results=warranty_results,
                )
                for r in discovered
            ]
            add_outcomes = await asyncio.gather(*add_tasks, return_exceptions=True)

            for r, outcome in zip(discovered, add_outcomes):
                if isinstance(outcome, Exception):
                    r["status"] = "error"
                    r["error"] = str(outcome)
                else:
                    r["added"] = True

    succeeded = [r for r in results if r["status"] == "ok"]
    failed = [r for r in results if r["status"] == "error"]

    if succeeded and show_discovered:
        table_data = [
            (
                r["hostname"],
                r["service_tag"],
                r["model"],
                "Added" if r.get("added") else "Discovered",
            )
            for r in succeeded
        ]
        headers = ["Hostname", "Service Tag", "Model", "Status"]
        print(tabulate(table_data, headers=headers, tablefmt="grid"))

    if succeeded:
        print(f"\nSucceeded: {len(succeeded)}")

    if failed:
        print(f"Failed: {len(failed)}")
        table_data = [(r["hostname"], r["error"]) for r in failed]
        headers = ["Hostname", "Error"]
        print(tabulate(table_data, headers=headers, tablefmt="grid"))

    total = len(results)
    print(f"Total: {total} hosts")


async def remove_dell_warranty(
    service_tag: Optional[str], hostname: Optional[str], warranty: str
) -> None:
    if service_tag:
        if debug_output:
            print(f"service_tag = {service_tag}")
    if hostname:
        if debug_output:
            print(f"hostname = {hostname}")

    db_initialize(warranty)

    with get_session() as session:
        if service_tag:
            results = session.query(System).filter(System.svc_tag == service_tag).all()
        elif hostname:
            results = session.query(System).filter(System.name == hostname).all()
        else:
            results = []

        if len(results) == 0:
            raise DatabaseError("No matching records found in database")
        if len(results) > 1:
            raise DatabaseError("Multiple matching records found in database")

        record = results[0]
        session.delete(record)
        session.commit()
        print("Record deleted")
