<?php

require_once __DIR__ . '/../../core/database.php';

class KpiTargetService
{
    private $pdo;

    public function __construct(?PDO $pdo = null)
    {
        $this->pdo = $pdo ?: db();
    }

    public function findForPeriod($kpiId, $periodFrom, $periodTo, $includeDraft = true)
    {
        $sql = "SELECT id,kpi_id,scope_type,scope_id,period_from,period_to,target_value,amber_threshold,red_threshold,status,notes,created_by_user_id,approved_by_user_id,approved_at,updated_by_user_id,created_at,updated_at
                FROM kpi_target
                WHERE kpi_id=?
                  AND scope_type='company'
                  AND scope_id IS NULL
                  AND period_from<=?
                  AND period_to>=?";
        $params = array($kpiId, $periodTo, $periodFrom);
        if (!$includeDraft) {
            $sql .= " AND status='approved'";
            $sql .= " ORDER BY period_from DESC, updated_at DESC, id DESC LIMIT 1";
        } else {
            $sql .= " ORDER BY updated_at DESC, id DESC LIMIT 1";
        }

        try {
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($params);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            return $row ?: null;
        } catch (Throwable $e) {
            return null;
        }
    }

    public function saveCompanyTarget(array $input, $userId, $dashboardKey)
    {
        $kpiId = trim((string)($input['kpi_id'] ?? ''));
        $periodFrom = trim((string)($input['period_from'] ?? ''));
        $periodTo = trim((string)($input['period_to'] ?? ''));
        $status = trim((string)($input['status'] ?? 'draft'));
        $notes = trim((string)($input['notes'] ?? ''));

        if (!preg_match('/^[A-Z0-9][A-Z0-9.\-_]{1,39}$/', $kpiId)) {
            throw new InvalidArgumentException('El identificador del KPI no es válido.');
        }
        if (!$this->validDate($periodFrom) || !$this->validDate($periodTo) || $periodFrom > $periodTo) {
            throw new InvalidArgumentException('La vigencia de la meta no es válida.');
        }
        if (!in_array($status, array('draft', 'approved', 'inactive'), true)) {
            throw new InvalidArgumentException('El estado de la meta no es válido.');
        }

        $targetValue = $this->requiredNumber($input['target_value'] ?? null, 'Debes ingresar el valor de la meta.');
        $amberThreshold = $this->optionalNumber($input['amber_threshold'] ?? null);
        $redThreshold = $this->optionalNumber($input['red_threshold'] ?? null);
        if (strlen($notes) > 1000) {
            $notes = substr($notes, 0, 1000);
        }

        $userId = (int)$userId;
        if ($userId <= 0) {
            throw new RuntimeException('No fue posible identificar al usuario.');
        }

        $this->pdo->beginTransaction();
        try {
            $existing = $this->findExactForUpdate($input['target_id'] ?? null, $kpiId, $periodFrom, $periodTo);
            $before = $existing ?: null;

            if ($existing) {
                $targetId = (int)$existing['id'];
                $stmt = $this->pdo->prepare("UPDATE kpi_target
                    SET period_from=?, period_to=?, target_value=?, amber_threshold=?, red_threshold=?, status=?, notes=?,
                        approved_by_user_id=?, approved_at=?, updated_by_user_id=?
                    WHERE id=?");
                $stmt->execute(array(
                    $periodFrom,
                    $periodTo,
                    $targetValue,
                    $amberThreshold,
                    $redThreshold,
                    $status,
                    $notes !== '' ? $notes : null,
                    $status === 'approved' ? $userId : null,
                    $status === 'approved' ? date('Y-m-d H:i:s') : null,
                    $userId,
                    $targetId,
                ));
            } else {
                $stmt = $this->pdo->prepare("INSERT INTO kpi_target
                    (kpi_id,scope_type,scope_id,period_from,period_to,target_value,amber_threshold,red_threshold,status,notes,created_by_user_id,approved_by_user_id,approved_at,updated_by_user_id)
                    VALUES (?,'company',NULL,?,?,?,?,?,?,?,?,?,?,?)");
                $stmt->execute(array(
                    $kpiId,
                    $periodFrom,
                    $periodTo,
                    $targetValue,
                    $amberThreshold,
                    $redThreshold,
                    $status,
                    $notes !== '' ? $notes : null,
                    $userId,
                    $status === 'approved' ? $userId : null,
                    $status === 'approved' ? date('Y-m-d H:i:s') : null,
                    $userId,
                ));
                $targetId = (int)$this->pdo->lastInsertId();
            }

            $saved = $this->findById($targetId);
            $this->insertHistory($saved, $userId);
            $this->insertAudit($before, $saved, $userId, $dashboardKey);
            $this->pdo->commit();
            return $saved;
        } catch (Throwable $e) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
            throw $e;
        }
    }

    public function upsertDefinition($kpiId, $name, $unit)
    {
        $stmt = $this->pdo->prepare("INSERT INTO kpi_definition
            (kpi_id,name,definition_version,unit,favorable_direction,is_active)
            VALUES (?,?, '1.0', ?, 'informative', 1)
            ON DUPLICATE KEY UPDATE name=VALUES(name), unit=VALUES(unit), is_active=1");
        $stmt->execute(array($kpiId, $name, $unit));
    }

    private function findExactForUpdate($targetId, $kpiId, $periodFrom, $periodTo)
    {
        if ((int)$targetId > 0) {
            $stmt = $this->pdo->prepare("SELECT * FROM kpi_target WHERE id=? AND kpi_id=? AND scope_type='company' AND scope_id IS NULL FOR UPDATE");
            $stmt->execute(array((int)$targetId, $kpiId));
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            if ($row) return $row;
        }

        $stmt = $this->pdo->prepare("SELECT * FROM kpi_target
            WHERE kpi_id=? AND scope_type='company' AND scope_id IS NULL AND period_from=? AND period_to=?
            ORDER BY id DESC LIMIT 1 FOR UPDATE");
        $stmt->execute(array($kpiId, $periodFrom, $periodTo));
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    }

    private function findById($targetId)
    {
        $stmt = $this->pdo->prepare("SELECT id,kpi_id,scope_type,scope_id,period_from,period_to,target_value,amber_threshold,red_threshold,status,notes,created_by_user_id,approved_by_user_id,approved_at,updated_by_user_id,created_at,updated_at FROM kpi_target WHERE id=?");
        $stmt->execute(array((int)$targetId));
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row) {
            throw new RuntimeException('La meta guardada no pudo volver a consultarse.');
        }
        return $row;
    }

    private function insertHistory(array $saved, $userId)
    {
        $stmt = $this->pdo->prepare('SELECT version_no FROM kpi_target_history WHERE target_id=? ORDER BY version_no DESC LIMIT 1 FOR UPDATE');
        $stmt->execute(array((int)$saved['id']));
        $lastVersion = $stmt->fetchColumn();
        $version = $lastVersion === false ? 1 : ((int)$lastVersion + 1);

        $stmt = $this->pdo->prepare("INSERT INTO kpi_target_history
            (target_id,version_no,kpi_id,scope_type,scope_id,period_from,period_to,target_value,amber_threshold,red_threshold,status,notes,changed_by_user_id)
            VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)");
        $stmt->execute(array(
            (int)$saved['id'], $version, $saved['kpi_id'], $saved['scope_type'], $saved['scope_id'],
            $saved['period_from'], $saved['period_to'], $saved['target_value'], $saved['amber_threshold'],
            $saved['red_threshold'], $saved['status'], $saved['notes'], (int)$userId,
        ));
    }

    private function insertAudit($before, array $after, $userId, $dashboardKey)
    {
        $remoteAddress = isset($_SERVER['REMOTE_ADDR']) ? substr((string)$_SERVER['REMOTE_ADDR'], 0, 64) : null;
        $sessionKey = session_status() === PHP_SESSION_ACTIVE && session_id() !== ''
            ? hash('sha256', session_id())
            : null;
        $stmt = $this->pdo->prepare("INSERT INTO dashboard_audit_log
            (user_id,action_key,dashboard_key,entity_type,entity_id,before_json,after_json,filters_json,ip_address,session_key)
            VALUES (?,?,?,?,?,?,?,?,?,?)");
        $stmt->execute(array(
            (int)$userId,
            $before ? 'kpi_target.updated' : 'kpi_target.created',
            $dashboardKey,
            'kpi_target',
            (string)$after['id'],
            $before ? json_encode($before, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : null,
            json_encode($after, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
            json_encode(array('scope_type' => 'company'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
            $remoteAddress,
            $sessionKey,
        ));
    }

    private function requiredNumber($value, $message)
    {
        if ($value === null || $value === '') {
            throw new InvalidArgumentException($message);
        }
        return $this->parseNumber($value);
    }

    private function optionalNumber($value)
    {
        if ($value === null || $value === '') return null;
        return $this->parseNumber($value);
    }

    private function parseNumber($value)
    {
        if (!is_numeric($value)) {
            throw new InvalidArgumentException('La meta debe ser un número válido.');
        }
        $number = (float)$value;
        if (!is_finite($number) || abs($number) > 99999999999999) {
            throw new InvalidArgumentException('El valor de la meta está fuera del rango permitido.');
        }
        return $number;
    }

    private function validDate($value)
    {
        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', (string)$value)) return false;
        $date = DateTime::createFromFormat('!Y-m-d', (string)$value);
        return $date && $date->format('Y-m-d') === $value;
    }
}
