package db

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"time"

	"github.com/jmoiron/sqlx"
	_ "modernc.org/sqlite"
	"golang.org/x/crypto/bcrypt"
)

type DB struct {
	*sqlx.DB
}

func Init(path string) (*DB, error) {
	// Ensure directory exists
	dir := filepath.Dir(path)
	if dir != "." && dir != "/" {
		if err := os.MkdirAll(dir, 0755); err != nil {
			return nil, fmt.Errorf("failed to create database directory: %w", err)
		}
	}

	// Connect to SQLite
	dsn := fmt.Sprintf("file:%s?_pragma=foreign_keys(1)", path)
	db, err := sqlx.Connect("sqlite", dsn)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to database: %w", err)
	}

	instance := &DB{db}

	// Run migrations
	if err := instance.RunMigrations(); err != nil {
		return nil, fmt.Errorf("failed to run migrations: %w", err)
	}

	return instance, nil
}

func (db *DB) RunMigrations() error {
	// Tables creation
	queries := []string{
		`CREATE TABLE IF NOT EXISTS clients (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            client_id TEXT UNIQUE NOT NULL,
            name TEXT,
            description TEXT,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            is_active BOOLEAN DEFAULT TRUE,
            rate_limit_per_minute INTEGER DEFAULT 60,
            total_requests INTEGER DEFAULT 0,
            total_tokens INTEGER DEFAULT 0,
            total_cost REAL DEFAULT 0.0
        )`,
		`CREATE TABLE IF NOT EXISTS llm_requests (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
            client_id TEXT,
            provider TEXT,
            model TEXT,
            prompt_tokens INTEGER,
            completion_tokens INTEGER,
            reasoning_tokens INTEGER DEFAULT 0,
            total_tokens INTEGER,
            cost REAL,
            has_images BOOLEAN DEFAULT FALSE,
            status TEXT DEFAULT 'success',
            error_message TEXT,
            duration_ms INTEGER,
            request_body TEXT,
            response_body TEXT,
            cache_read_tokens INTEGER DEFAULT 0,
            cache_write_tokens INTEGER DEFAULT 0,
            FOREIGN KEY (client_id) REFERENCES clients(client_id) ON DELETE SET NULL
        )`,
		`CREATE TABLE IF NOT EXISTS provider_configs (
            id TEXT PRIMARY KEY,
            display_name TEXT NOT NULL,
            enabled BOOLEAN DEFAULT TRUE,
            base_url TEXT,
            api_key TEXT,
            credit_balance REAL DEFAULT 0.0,
            low_credit_threshold REAL DEFAULT 5.0,
            billing_mode TEXT,
            api_key_encrypted BOOLEAN DEFAULT FALSE,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )`,
		`CREATE TABLE IF NOT EXISTS model_configs (
            id TEXT PRIMARY KEY,
            provider_id TEXT NOT NULL,
            display_name TEXT,
            enabled BOOLEAN DEFAULT TRUE,
            prompt_cost_per_m REAL,
            completion_cost_per_m REAL,
            cache_read_cost_per_m REAL,
            cache_write_cost_per_m REAL,
            mapping TEXT,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (provider_id) REFERENCES provider_configs(id) ON DELETE CASCADE
        )`,
		`CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            password_hash TEXT NOT NULL,
            display_name TEXT,
            role TEXT DEFAULT 'admin',
            must_change_password BOOLEAN DEFAULT FALSE,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )`,
		`CREATE TABLE IF NOT EXISTS client_tokens (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            client_id TEXT NOT NULL,
            token TEXT NOT NULL UNIQUE,
            name TEXT DEFAULT 'default',
            is_active BOOLEAN DEFAULT TRUE,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            last_used_at DATETIME,
            FOREIGN KEY (client_id) REFERENCES clients(client_id) ON DELETE CASCADE
        )`,
		`CREATE TABLE IF NOT EXISTS model_groups (
            id TEXT PRIMARY KEY,
            strategy TEXT NOT NULL DEFAULT 'heuristic',
            selector_model TEXT,
            targets TEXT NOT NULL DEFAULT '[]',
            complexity_threshold INTEGER,
            heuristic_rules TEXT,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )`,
	}

	for _, q := range queries {
		if _, err := db.Exec(q); err != nil {
			return fmt.Errorf("migration failed for query [%s]: %w", q, err)
		}
	}

	// Add indices
	indices := []string{
		"CREATE INDEX IF NOT EXISTS idx_clients_client_id ON clients(client_id)",
		"CREATE INDEX IF NOT EXISTS idx_clients_created_at ON clients(created_at)",
		"CREATE INDEX IF NOT EXISTS idx_llm_requests_timestamp ON llm_requests(timestamp)",
		"CREATE INDEX IF NOT EXISTS idx_llm_requests_client_id ON llm_requests(client_id)",
		"CREATE INDEX IF NOT EXISTS idx_llm_requests_provider ON llm_requests(provider)",
		"CREATE INDEX IF NOT EXISTS idx_llm_requests_status ON llm_requests(status)",
		"CREATE UNIQUE INDEX IF NOT EXISTS idx_client_tokens_token ON client_tokens(token)",
		"CREATE INDEX IF NOT EXISTS idx_client_tokens_client_id ON client_tokens(client_id)",
		"CREATE INDEX IF NOT EXISTS idx_llm_requests_client_timestamp ON llm_requests(client_id, timestamp)",
		"CREATE INDEX IF NOT EXISTS idx_llm_requests_provider_timestamp ON llm_requests(provider, timestamp)",
		"CREATE INDEX IF NOT EXISTS idx_model_configs_provider_id ON model_configs(provider_id)",
	}

	for _, idx := range indices {
		if _, err := db.Exec(idx); err != nil {
			return fmt.Errorf("failed to create index [%s]: %w", idx, err)
		}
	}

	// Default admin user
	var count int
	if err := db.Get(&count, "SELECT COUNT(*) FROM users"); err != nil {
		return fmt.Errorf("failed to count users: %w", err)
	}

	if count == 0 {
		hash, err := bcrypt.GenerateFromPassword([]byte("admin123"), 12)
		if err != nil {
			return fmt.Errorf("failed to hash default password: %w", err)
		}
		_, err = db.Exec("INSERT INTO users (username, password_hash, role, must_change_password) VALUES ('admin', ?, 'admin', 1)", string(hash))
		if err != nil {
			return fmt.Errorf("failed to insert default admin: %w", err)
		}
		log.Println("Created default admin user with password 'admin123' (must change on first login)")
	}

	// Default client
	_, err := db.Exec(`INSERT OR IGNORE INTO clients (client_id, name, description)
        VALUES ('default', 'Default Client', 'Default client for anonymous requests')`)
	if err != nil {
		return fmt.Errorf("failed to insert default client: %w", err)
	}

	// Seed default model groups
	defaultGroups := []struct {
		id, strategy, targets string
	}{
		{"deepseek-auto", "heuristic", `["deepseek-chat","deepseek-reasoner"]`},
		{"openai-auto", "heuristic", `["gpt-4o-mini","gpt-4o"]`},
		{"gemini-auto", "heuristic", `["gemini-2.0-flash","gemini-2.5-pro"]`},
	}
	for _, g := range defaultGroups {
		db.Exec(`INSERT OR IGNORE INTO model_groups (id, strategy, targets) VALUES (?, ?, ?)`,
			g.id, g.strategy, g.targets)
	}

	return nil
}

// Data models for DB tables

type Client struct {
	ID                  int       `db:"id"`
	ClientID            string    `db:"client_id"`
	Name                *string   `db:"name"`
	Description         *string   `db:"description"`
	CreatedAt           time.Time `db:"created_at"`
	UpdatedAt           time.Time `db:"updated_at"`
	IsActive            bool      `db:"is_active"`
	RateLimitPerMinute  int       `db:"rate_limit_per_minute"`
	TotalRequests       int       `db:"total_requests"`
	TotalTokens         int       `db:"total_tokens"`
	TotalCost           float64   `db:"total_cost"`
}

type LLMRequest struct {
	ID               int       `db:"id"`
	Timestamp        time.Time `db:"timestamp"`
	ClientID         *string   `db:"client_id"`
	Provider         *string   `db:"provider"`
	Model            *string   `db:"model"`
	PromptTokens     *int      `db:"prompt_tokens"`
	CompletionTokens *int      `db:"completion_tokens"`
	ReasoningTokens  int       `db:"reasoning_tokens"`
	TotalTokens      *int      `db:"total_tokens"`
	Cost             *float64  `db:"cost"`
	HasImages        bool      `db:"has_images"`
	Status           string    `db:"status"`
	ErrorMessage     *string   `db:"error_message"`
	DurationMS       *int      `db:"duration_ms"`
	RequestBody      *string   `db:"request_body"`
	ResponseBody     *string   `db:"response_body"`
	CacheReadTokens  int       `db:"cache_read_tokens"`
	CacheWriteTokens int       `db:"cache_write_tokens"`
}

type ProviderConfig struct {
	ID                 string    `db:"id"`
	DisplayName        string    `db:"display_name"`
	Enabled            bool      `db:"enabled"`
	BaseURL            *string   `db:"base_url"`
	APIKey             *string   `db:"api_key"`
	CreditBalance      float64   `db:"credit_balance"`
	LowCreditThreshold float64   `db:"low_credit_threshold"`
	BillingMode        *string   `db:"billing_mode"`
	APIKeyEncrypted    bool      `db:"api_key_encrypted"`
	UpdatedAt          time.Time `db:"updated_at"`
}

type ModelConfig struct {
	ID                  string    `db:"id"`
	ProviderID          string    `db:"provider_id"`
	DisplayName         *string   `db:"display_name"`
	Enabled             bool      `db:"enabled"`
	PromptCostPerM      *float64  `db:"prompt_cost_per_m"`
	CompletionCostPerM  *float64  `db:"completion_cost_per_m"`
	CacheReadCostPerM   *float64  `db:"cache_read_cost_per_m"`
	CacheWriteCostPerM  *float64  `db:"cache_write_cost_per_m"`
	Mapping             *string   `db:"mapping"`
	UpdatedAt           time.Time `db:"updated_at"`
}

type User struct {
	ID                 int       `db:"id" json:"id"`
	Username           string    `db:"username" json:"username"`
	PasswordHash       string    `db:"password_hash" json:"-"`
	DisplayName        *string   `db:"display_name" json:"display_name"`
	Role               string    `db:"role" json:"role"`
	MustChangePassword bool      `db:"must_change_password" json:"must_change_password"`
	CreatedAt          time.Time `db:"created_at" json:"created_at"`
}

type ClientToken struct {
	ID        int        `db:"id"`
	ClientID  string     `db:"client_id"`
	Token     string     `db:"token"`
	Name      string     `db:"name"`
	IsActive  bool       `db:"is_active"`
	CreatedAt time.Time  `db:"created_at"`
	LastUsedAt *time.Time `db:"last_used_at"`
}

type ModelGroup struct {
	ID                  string    `db:"id" json:"id"`
	Strategy            string    `db:"strategy" json:"strategy"`
	SelectorModel       *string   `db:"selector_model" json:"selector_model"`
	Targets             string    `db:"targets" json:"targets"` // JSON array
	ComplexityThreshold *int      `db:"complexity_threshold" json:"complexity_threshold"`
	HeuristicRules      *string   `db:"heuristic_rules" json:"heuristic_rules"`
	CreatedAt           time.Time `db:"created_at" json:"created_at"`
	UpdatedAt           time.Time `db:"updated_at" json:"updated_at"`
}
