"""CLI commands for the homeostat pipeline."""

import logging
import os
import sqlite3
from argparse import ArgumentParser
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any

from byteb4rb1e.utils.argparse.command import CLICommand

log = logging.getLogger(__name__)


@dataclass
class AuthorizeCommand(CLICommand):
    name: str = "authorize"
    help: str = "Run Fitbit OAuth 2.0 authorization flow"

    def add_arguments(self, parser: ArgumentParser) -> None:
        parser.add_argument(
            "--redirect-uri",
            default=os.environ.get(
                "FITBIT_REDIRECT_URI", "http://localhost:8080"
            ),
            help="OAuth redirect URI",
        )

    def execute(self, args: Any) -> int:
        from byteb4rb1e.homeostat.adapters.fitbit import (
            FitbitAdapter,
            TokenStore,
        )

        client_id = os.environ.get("FITBIT_CLIENT_ID")
        client_secret = os.environ.get("FITBIT_CLIENT_SECRET")
        if not client_id or not client_secret:
            log.error(
                "Set FITBIT_CLIENT_ID and"
                " FITBIT_CLIENT_SECRET"
            )
            return 1

        adapter = FitbitAdapter(
            client_id=client_id,
            client_secret=client_secret,
            token_store=TokenStore(
                path=Path(".fitbit_tokens.json")
            ),
        )
        adapter.authorize(args.redirect_uri)
        log.info("Authorization complete.")
        return 0


@dataclass
class SyncCommand(CLICommand):
    name: str = "sync"
    help: str = "Fetch data from sources and write to warehouse"

    def add_arguments(self, parser: ArgumentParser) -> None:
        parser.add_argument(
            "--date",
            default=datetime.now().strftime("%Y-%m-%d"),
            help="Date to sync (YYYY-MM-DD)",
        )
        parser.add_argument(
            "--source",
            choices=["fitbit", "fddb", "all"],
            default="all",
            help="Source to sync",
        )
        parser.add_argument(
            "--data-dir",
            type=Path,
            default=Path("data"),
            help="Warehouse root directory",
        )

    def execute(self, args: Any) -> int:
        date = args.date
        data_dir = args.data_dir
        fetched_at = datetime.now(timezone.utc).isoformat()

        if args.source in ("fitbit", "all"):
            rc = self._sync_fitbit(date, data_dir, fetched_at)
            if rc != 0:
                return rc

        if args.source in ("fddb", "all"):
            rc = self._sync_fddb(date, data_dir, fetched_at)
            if rc != 0:
                return rc

        log.info("Sync complete for %s.", date)
        return 0

    def _sync_fitbit(
        self, date: str, data_dir: Path, fetched_at: str
    ) -> int:
        from byteb4rb1e.homeostat.adapters.fitbit import (
            FitbitAdapter,
            TokenStore,
        )
        from byteb4rb1e.homeostat.transformers.fitbit import (
            transform_activities,
            transform_body,
            transform_calories,
            transform_heart_rate,
            transform_sleep,
            transform_steps,
        )
        from byteb4rb1e.homeostat.warehouse.canonical import (
            CanonicalWarehouse,
        )
        from byteb4rb1e.homeostat.warehouse.raw import (
            RawWarehouse,
        )

        client_id = os.environ.get("FITBIT_CLIENT_ID")
        client_secret = os.environ.get("FITBIT_CLIENT_SECRET")
        if not client_id or not client_secret:
            log.error(
                "Set FITBIT_CLIENT_ID and"
                " FITBIT_CLIENT_SECRET"
            )
            return 1

        adapter = FitbitAdapter(
            client_id=client_id,
            client_secret=client_secret,
            token_store=TokenStore(
                path=Path(".fitbit_tokens.json")
            ),
        )

        raw_wh = RawWarehouse(root=data_dir)
        canonical_wh = CanonicalWarehouse(root=data_dir)

        log.info("Fetching Fitbit data for %s...", date)

        steps_env = adapter.steps(date)
        raw_wh.append(steps_env, date)
        records = transform_steps(
            steps_env.payload, date, fetched_at
        )
        canonical_wh.append_many(
            "step-snapshot", records, date
        )
        log.info("  Steps: %d", records[0].steps if records else 0)

        cal_env = adapter.calories(date)
        raw_wh.append(cal_env, date)
        records = transform_calories(
            cal_env.payload, date, fetched_at
        )
        canonical_wh.append_many(
            "calorie-snapshot", records, date
        )

        act_env = adapter.activities(date)
        raw_wh.append(act_env, date)
        am, al, dist, sess, goals = transform_activities(
            act_env.payload, date, fetched_at
        )
        canonical_wh.append_many(
            "active-minutes-snapshot", am, date
        )
        canonical_wh.append_many(
            "activity-level-snapshot", al, date
        )
        canonical_wh.append_many(
            "distance-snapshot", dist, date
        )
        canonical_wh.append_many(
            "activity-session", sess, date
        )
        canonical_wh.append_many(
            "goal-snapshot", goals, date
        )

        sleep_env = adapter.sleep(date)
        raw_wh.append(sleep_env, date)
        sessions, stages = transform_sleep(
            sleep_env.payload, date, fetched_at
        )
        canonical_wh.append_many(
            "sleep-session", sessions, date
        )
        canonical_wh.append_many(
            "sleep-stage", stages, date
        )
        log.info(
            "  Sleep: %d sessions", len(sessions)
        )

        body_env = adapter.body(date)
        raw_wh.append(body_env, date)
        body_records = transform_body(
            body_env.payload, date, fetched_at
        )
        canonical_wh.append_many(
            "body-measurement", body_records, date
        )

        # Heart rate intraday
        now = datetime.now()
        start_t = "00:00"
        end_t = now.strftime("%H:%M")
        try:
            hr_env = adapter.heart_rate_intraday(
                date, start_t, end_t
            )
            raw_wh.append(hr_env, date)
            hr_records, rhr_records = transform_heart_rate(
                hr_env.payload, date, fetched_at
            )
            canonical_wh.append_many(
                "heart-rate-sample", hr_records, date
            )
            canonical_wh.append_many(
                "resting-heart-rate-sample",
                rhr_records, date,
            )
            log.info(
                "  Heart rate: %d samples,"
                " resting: %s",
                len(hr_records),
                rhr_records[0].bpm
                if rhr_records else "n/a",
            )
        except Exception as e:
            log.warning("  Heart rate intraday: %s", e)

        log.info("Fitbit sync complete.")
        return 0

    def _sync_fddb(
        self, date: str, data_dir: Path, fetched_at: str
    ) -> int:
        from byteb4rb1e.homeostat.adapters.fddb import (
            FddbAdapter,
            LoginFailed,
        )
        from byteb4rb1e.homeostat.transformers.fddb import (
            transform_day_detail,
            transform_month_diary,
        )
        from byteb4rb1e.homeostat.warehouse.canonical import (
            CanonicalWarehouse,
        )
        from byteb4rb1e.homeostat.warehouse.raw import (
            RawWarehouse,
        )

        username = os.environ.get("FDDB_USERNAME")
        password = os.environ.get("FDDB_PASSWORD")
        if not username or not password:
            log.error(
                "Set FDDB_USERNAME and FDDB_PASSWORD"
            )
            return 1

        adapter = FddbAdapter(
            username=username, password=password
        )

        log.info("Logging in to fddb.info...")
        try:
            adapter.login()
        except LoginFailed:
            log.error("fddb login failed.")
            return 1

        raw_wh = RawWarehouse(root=data_dir)
        canonical_wh = CanonicalWarehouse(root=data_dir)

        dt = datetime.strptime(date, "%Y-%m-%d")
        month_env = adapter.month_diary(dt.month, dt.year)
        raw_wh.append(month_env, date)

        days = transform_month_diary(
            month_env.payload,
            dt.month,
            dt.year,
            fetched_at,
        )

        if date not in days:
            log.info("No fddb diary data for %s.", date)
            return 0

        day_data = days[date]
        if day_data.get("detail_url"):
            detail_env = adapter.day_detail(
                day_data["detail_url"]
            )
            raw_wh.append(detail_env, date)

            nutrition_days, meals, meal_items = (
                transform_day_detail(
                    detail_env.payload,
                    date,
                    fetched_at,
                    day_data,
                )
            )
        else:
            nutrition_days, meals, meal_items = (
                transform_day_detail(
                    "",
                    date,
                    fetched_at,
                    day_data,
                )
            )

        canonical_wh.append_many(
            "nutrition-day", nutrition_days, date
        )
        canonical_wh.append_many("meal", meals, date)
        canonical_wh.append_many(
            "meal-item", meal_items, date
        )
        log.info(
            "fddb sync complete: %d meals, %d items.",
            len(meals), len(meal_items),
        )
        return 0


@dataclass
class ProjectCommand(CLICommand):
    name: str = "project"
    help: str = "Build projections from warehouse data"

    def add_arguments(self, parser: ArgumentParser) -> None:
        from byteb4rb1e.homeostat.projections.config import (
            ProjectionConfig,
        )
        from byteb4rb1e.homeostat.util.config import (
            add_config_arguments,
        )

        parser.add_argument(
            "--date",
            default=None,
            help="Date for status.json (default: latest)",
        )
        parser.add_argument(
            "--data-dir",
            type=Path,
            default=Path("data"),
            help="Warehouse root directory",
        )
        parser.add_argument(
            "--build-dir",
            type=Path,
            default=Path("build"),
            help="Build output directory",
        )
        parser.add_argument(
            "--config",
            type=Path,
            default=Path(".homeostat.ini"),
            help="INI config file for projection settings",
        )
        add_config_arguments(ProjectionConfig, parser)

    def execute(self, args: Any) -> int:
        from byteb4rb1e.homeostat.projections.builder import (
            ProjectionBuilder,
        )
        from byteb4rb1e.homeostat.projections.config import (
            ProjectionConfig,
        )
        from byteb4rb1e.homeostat.util.config import (
            apply_cli_overrides,
            ensure_ini,
        )
        from byteb4rb1e.homeostat.warehouse.canonical import (
            CanonicalWarehouse,
        )

        config = ensure_ini(
            ProjectionConfig, args.config
        )

        config = apply_cli_overrides(config, args)
        log.info(
            "Projection config: HR %dmin, steps %dmin,"
            " calories %dmin, history %d days",
            config.heart_rate_resolution,
            config.step_resolution,
            config.calorie_resolution,
            config.history_days,
        )

        wh = CanonicalWarehouse(root=args.data_dir)
        db = sqlite3.connect(":memory:")
        count = wh.hydrate(db)
        log.info("Hydrated %d canonical rows.", count)

        builder = ProjectionBuilder(
            db=db, build_root=args.build_dir,
            config=config,
        )
        out = builder.build(args.date)
        log.info("Projection built: %s", out)

        current = args.build_dir / "projection" / "current"
        log.info("Current symlink: %s", current)
        return 0


@dataclass
class MeasureCommand(CLICommand):
    name: str = "measure"
    help: str = "Log manual body measurements to warehouse"

    def add_arguments(self, parser: ArgumentParser) -> None:
        parser.add_argument(
            "--date",
            default=datetime.now().strftime("%Y-%m-%d"),
            help="Date to log for (YYYY-MM-DD)",
        )
        parser.add_argument(
            "--data-dir",
            type=Path,
            default=Path("data"),
            help="Warehouse root directory",
        )
        parser.add_argument(
            "--weight", type=float, default=None,
            help="Weight in kg",
        )
        parser.add_argument(
            "--body-fat", type=float, default=None,
            help="Body fat percentage",
        )
        parser.add_argument(
            "--muscle-mass", type=float, default=None,
            help="Muscle mass in kg",
        )
        parser.add_argument(
            "--height", type=float, default=1.80,
            help="Height in meters for BMI (default: 1.80)",
        )
        parser.add_argument(
            "--chest", type=float, default=None,
            help="Chest circumference in cm",
        )
        parser.add_argument(
            "--waist", type=float, default=None,
            help="Waist circumference in cm",
        )
        parser.add_argument(
            "--hips", type=float, default=None,
            help="Hip circumference in cm",
        )
        parser.add_argument(
            "--bicep", type=float, default=None,
            help="Bicep circumference in cm",
        )
        parser.add_argument(
            "--thigh", type=float, default=None,
            help="Thigh circumference in cm",
        )

    def execute(self, args: Any) -> int:
        from byteb4rb1e.homeostat.warehouse.canonical import (
            CanonicalWarehouse,
        )
        from byteb4rb1e.homeostat.warehouse.schema import (
            BodyMeasurement,
        )

        has_input = any([
            args.weight is not None,
            args.body_fat is not None,
            args.muscle_mass is not None,
            args.chest is not None,
            args.waist is not None,
            args.hips is not None,
            args.bicep is not None,
            args.thigh is not None,
        ])
        if not has_input:
            log.error("Provide at least one measurement.")
            return 1

        weight = args.weight or 0.0
        bmi = 0.0
        if weight > 0 and args.height > 0:
            bmi = round(
                weight / (args.height ** 2), 1
            )

        fetched_at = datetime.now(
            timezone.utc
        ).isoformat()

        record = BodyMeasurement(
            source="manual",
            fetched_at=fetched_at,
            date=args.date,
            weight_kg=weight,
            body_fat_pct=args.body_fat or 0.0,
            bmi=bmi,
            muscle_mass_kg=args.muscle_mass or 0.0,
            chest_cm=args.chest or 0.0,
            waist_cm=args.waist or 0.0,
            hips_cm=args.hips or 0.0,
            bicep_cm=args.bicep or 0.0,
            thigh_cm=args.thigh or 0.0,
        )

        wh = CanonicalWarehouse(root=args.data_dir)
        wh.append("body-measurement", record, args.date)

        log.info(
            "Logged measurement for %s.", args.date
        )
        return 0


@dataclass
class DrinkCommand(CLICommand):
    name: str = "drink"
    help: str = "Log water intake to warehouse"

    def add_arguments(self, parser: ArgumentParser) -> None:
        parser.add_argument(
            "--date",
            default=datetime.now().strftime("%Y-%m-%d"),
            help="Date to log for (YYYY-MM-DD)",
        )
        parser.add_argument(
            "--data-dir",
            type=Path,
            default=Path("data"),
            help="Warehouse root directory",
        )
        parser.add_argument(
            "amount_ml", type=int,
            help="Amount of water in millilitres",
        )

    def execute(self, args: Any) -> int:
        from byteb4rb1e.homeostat.warehouse.canonical import (
            CanonicalWarehouse,
        )
        from byteb4rb1e.homeostat.warehouse.schema import (
            WaterIntake,
        )

        if args.amount_ml <= 0:
            log.error("Amount must be positive.")
            return 1

        fetched_at = datetime.now(
            timezone.utc
        ).isoformat()

        record = WaterIntake(
            source="manual",
            fetched_at=fetched_at,
            date=args.date,
            amount_ml=args.amount_ml,
        )

        wh = CanonicalWarehouse(root=args.data_dir)
        wh.append("water-intake", record, args.date)

        log.info(
            "Logged %d ml water for %s.",
            args.amount_ml, args.date,
        )
        return 0


@dataclass
class MigrateCommand(CLICommand):
    name: str = "migrate"
    help: str = (
        "Migrate legacy JSON into canonical warehouse"
    )

    def add_arguments(self, parser: ArgumentParser) -> None:
        parser.add_argument(
            "--data-dir",
            type=Path,
            default=Path("data"),
            help="Root directory containing legacy JSON"
                 " and canonical warehouse output",
        )

    def execute(self, args: Any) -> int:
        import json as _json

        from byteb4rb1e.homeostat.warehouse.canonical import (
            CanonicalWarehouse,
        )
        from byteb4rb1e.homeostat.warehouse.schema import (
            ActiveMinutesSnapshot,
            ActivityLevelSnapshot,
            ActivitySession,
            BodyMeasurement,
            CalorieSnapshot,
            DistanceSnapshot,
            GoalSnapshot,
            Meal,
            MealItem,
            NutritionDay,
            RestingHeartRateSample,
            SleepSession,
            SleepStage,
            StepSnapshot,
        )

        data_dir = args.data_dir
        wh = CanonicalWarehouse(root=data_dir)
        fetched_at = "1970-01-01T00:00:00+00:00"
        src = "legacy"
        total = 0

        def _load(path: Path) -> dict:
            if path.exists():
                return _json.loads(
                    path.read_text(encoding="utf-8")
                )
            return {}

        # -- status.json goals --------------------------------

        status = _load(data_dir / "status.json")
        vitals = status.get("vitals", {})
        if vitals.get("steps_goal"):
            updated = status.get(
                "last_updated", ""
            )[:10]
            if updated:
                wh.append(
                    "goal-snapshot",
                    GoalSnapshot(
                        source=src,
                        fetched_at=fetched_at,
                        date=updated,
                        observed_at=fetched_at,
                        steps_goal=vitals.get(
                            "steps_goal", 0
                        ),
                        active_minutes_goal=vitals.get(
                            "active_minutes_goal", 0
                        ),
                    ),
                    updated,
                )
                total += 1

        # -- history/vitals.json ------------------------------

        vitals_h = _load(
            data_dir / "history" / "vitals.json"
        )
        for day in vitals_h.get("days", []):
            d = day["date"]

            if day.get("steps"):
                wh.append(
                    "step-snapshot",
                    StepSnapshot(
                        source=src,
                        fetched_at=fetched_at,
                        date=d,
                        observed_at=fetched_at,
                        steps=day["steps"],
                    ), d,
                )
                total += 1

            if day.get("calories_burned"):
                wh.append(
                    "calorie-snapshot",
                    CalorieSnapshot(
                        source=src,
                        fetched_at=fetched_at,
                        date=d,
                        observed_at=fetched_at,
                        calories=day[
                            "calories_burned"
                        ],
                    ), d,
                )
                total += 1

            rhr = day.get("resting_hr", 0)
            if rhr:
                wh.append(
                    "resting-heart-rate-sample",
                    RestingHeartRateSample(
                        source=src,
                        fetched_at=fetched_at,
                        date=d,
                        bpm=rhr,
                    ), d,
                )
                total += 1

        # -- history/activity.json ----------------------------

        activity_h = _load(
            data_dir / "history" / "activity.json"
        )
        for day in activity_h.get("days", []):
            d = day["date"]
            fairly = day.get(
                "fairly_active_minutes", 0
            )
            very = day.get(
                "very_active_minutes", 0
            )

            wh.append(
                "active-minutes-snapshot",
                ActiveMinutesSnapshot(
                    source=src,
                    fetched_at=fetched_at,
                    date=d,
                    observed_at=fetched_at,
                    fairly_active=fairly,
                    very_active=very,
                ), d,
            )
            total += 1

            wh.append(
                "activity-level-snapshot",
                ActivityLevelSnapshot(
                    source=src,
                    fetched_at=fetched_at,
                    date=d,
                    observed_at=fetched_at,
                    sedentary_minutes=day.get(
                        "sedentary_minutes", 0
                    ),
                    lightly_active_minutes=day.get(
                        "lightly_active_minutes", 0
                    ),
                    fairly_active_minutes=fairly,
                    very_active_minutes=very,
                ), d,
            )
            total += 1

            wh.append(
                "distance-snapshot",
                DistanceSnapshot(
                    source=src,
                    fetched_at=fetched_at,
                    date=d,
                    observed_at=fetched_at,
                    distance_km=day.get(
                        "distance_km", 0.0
                    ),
                    floors=day.get("floors", 0),
                    elevation=day.get(
                        "elevation", 0.0
                    ),
                ), d,
            )
            total += 1

            for act in day.get(
                "logged_activities", []
            ):
                wh.append(
                    "activity-session",
                    ActivitySession(
                        source=src,
                        fetched_at=fetched_at,
                        date=d,
                        start_time=act.get(
                            "start_time", ""
                        ),
                        name=act.get(
                            "name", "Unknown"
                        ),
                        calories=act.get(
                            "calories", 0
                        ),
                        duration_min=act.get(
                            "duration_min", 0.0
                        ),
                        steps=act.get("steps", 0),
                    ), d,
                )
                total += 1

        # -- history/sleep.json -------------------------------

        sleep_h = _load(
            data_dir / "history" / "sleep.json"
        )
        for day in sleep_h.get("days", []):
            d = day["date"]
            dur = day.get("duration_hours", 0)
            if not dur:
                continue

            wh.append(
                "sleep-session",
                SleepSession(
                    source=src,
                    fetched_at=fetched_at,
                    date=d,
                    bedtime="",
                    wake_time="",
                    duration_hours=dur,
                    score=day.get("score", 0),
                    is_main=True,
                ), d,
            )
            total += 1

            stages = day.get("stages", {})
            for stage_name, hours in stages.items():
                if hours:
                    wh.append(
                        "sleep-stage",
                        SleepStage(
                            source=src,
                            fetched_at=fetched_at,
                            date=d,
                            bedtime="",
                            stage=stage_name,
                            minutes=round(
                                hours * 60
                            ),
                        ), d,
                    )
                    total += 1

        # -- history/nutrition.json ---------------------------

        nutrition_h = _load(
            data_dir / "history" / "nutrition.json"
        )
        for day in nutrition_h.get("days", []):
            d = day["date"]
            macros = day.get("macros", {})

            wh.append(
                "nutrition-day",
                NutritionDay(
                    source=src,
                    fetched_at=fetched_at,
                    date=d,
                    calories_consumed=day.get(
                        "calories", 0
                    ),
                    calories_target=day.get(
                        "target", 0
                    ),
                    protein_g=macros.get(
                        "protein_g", 0.0
                    ),
                    carbs_g=macros.get(
                        "carbs_g", 0.0
                    ),
                    fat_g=macros.get(
                        "fat_g", 0.0
                    ),
                ), d,
            )
            total += 1

            for meal in day.get("meals", []):
                meal_name = meal.get("name", "")
                mm = meal.get("macros", {})

                wh.append(
                    "meal",
                    Meal(
                        source=src,
                        fetched_at=fetched_at,
                        date=d,
                        meal_name=meal_name,
                        calories=meal.get(
                            "calories", 0
                        ),
                        protein_g=mm.get(
                            "protein_g", 0.0
                        ),
                        carbs_g=mm.get(
                            "carbs_g", 0.0
                        ),
                        fat_g=mm.get(
                            "fat_g", 0.0
                        ),
                    ), d,
                )
                total += 1

                for item in meal.get("items", []):
                    wh.append(
                        "meal-item",
                        MealItem(
                            source=src,
                            fetched_at=fetched_at,
                            date=d,
                            meal_name=meal_name,
                            item_name=item,
                        ), d,
                    )
                    total += 1

        # -- history/measurements.json ------------------------

        meas_h = _load(
            data_dir / "history" / "measurements.json"
        )
        for day in meas_h.get("days", []):
            d = day["date"]
            wt = day.get("weight_kg", 0.0)
            bf = day.get("body_fat_pct", 0.0)
            bmi = day.get("bmi", 0.0)
            if not wt and not bf and not bmi:
                continue

            body = day.get("body") or {}
            wh.append(
                "body-measurement",
                BodyMeasurement(
                    source=src,
                    fetched_at=fetched_at,
                    date=d,
                    weight_kg=wt,
                    body_fat_pct=bf,
                    bmi=bmi,
                    muscle_mass_kg=day.get(
                        "muscle_mass_kg", 0.0
                    ),
                    chest_cm=body.get(
                        "chest_cm", 0.0
                    ),
                    waist_cm=body.get(
                        "waist_cm", 0.0
                    ),
                    hips_cm=body.get(
                        "hips_cm", 0.0
                    ),
                    bicep_cm=body.get(
                        "bicep_cm", 0.0
                    ),
                    thigh_cm=body.get(
                        "thigh_cm", 0.0
                    ),
                ), d,
            )
            total += 1

        log.info("Migrated %d records.", total)
        return 0
