package tools

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/modelcontextprotocol/go-sdk/mcp"

	"skraak_mcp/db"
	"skraak_mcp/utils"
)

// ImportFileInput defines the input parameters for the import_file tool
type ImportFileInput struct {
	FilePath   string `json:"file_path" jsonschema:"required,Absolute path to WAV file"`
	DatasetID  string `json:"dataset_id" jsonschema:"required,Dataset ID (12 characters)"`
	LocationID string `json:"location_id" jsonschema:"required,Location ID (12 characters)"`
	ClusterID  string `json:"cluster_id" jsonschema:"required,Cluster ID (12 characters)"`
}

// ImportFileOutput defines the output structure for the import_file tool
type ImportFileOutput struct {
	FileID         string    `json:"file_id" jsonschema:"Generated 21-character nanoid"`
	FileName       string    `json:"file_name" jsonschema:"Base filename"`
	Hash           string    `json:"hash" jsonschema:"XXH64 hash (16-character hex)"`
	Duration       float64   `json:"duration_seconds" jsonschema:"File duration in seconds"`
	SampleRate     int       `json:"sample_rate" jsonschema:"Sample rate in Hz"`
	TimestampLocal time.Time `json:"timestamp_local" jsonschema:"Local timestamp"`
	IsAudioMoth    bool      `json:"is_audiomoth" jsonschema:"AudioMoth detection"`
	IsDuplicate    bool      `json:"is_duplicate" jsonschema:"Skipped as duplicate"`
	ProcessingTime string    `json:"processing_time" jsonschema:"Duration string"`
	Error          *string   `json:"error,omitempty" jsonschema:"Error if failed"`
}

// locationData holds location information (local wrapper around utils types)
type locationData struct {
	Latitude   float64
	Longitude  float64
	TimezoneID string
}

// fileData holds file metadata (local wrapper around utils types)
type fileData struct {
	FileName       string
	Hash           string
	Duration       float64
	SampleRate     int
	TimestampLocal time.Time
	IsAudioMoth    bool
	MothData       *utils.AudioMothData
	AstroData      utils.AstronomicalData
}

// ImportFile implements the import_file MCP tool
// Imports a single WAV audio file into the database with full metadata extraction
func ImportFile(
	ctx context.Context,
	req *mcp.CallToolRequest,
	input ImportFileInput,
) (*mcp.CallToolResult, ImportFileOutput, error) {
	startTime := time.Now()
	var output ImportFileOutput

	// Phase 1: Validate file path
	_, err := validateFilePath(input.FilePath)
	if err != nil {
		return nil, output, fmt.Errorf("file validation failed: %w", err)
	}
	output.FileName = filepath.Base(input.FilePath)

	// Phase 2: Validate database hierarchy
	if err := validateImportInput(ImportAudioFilesInput{
		DatasetID:  input.DatasetID,
		LocationID: input.LocationID,
		ClusterID:  input.ClusterID,
		FolderPath: filepath.Dir(input.FilePath), // For validation only
	}, dbPath); err != nil {
		return nil, output, fmt.Errorf("hierarchy validation failed: %w", err)
	}

	// Phase 3: Get location data for astronomical calculations
	locationData, err := getLocationData(dbPath, input.LocationID)
	if err != nil {
		return nil, output, fmt.Errorf("failed to get location data: %w", err)
	}

	// Phase 4: Process file metadata
	fileData, err := processFile(input.FilePath, locationData)
	if err != nil {
		errMsg := err.Error()
		output.Error = &errMsg
		output.ProcessingTime = time.Since(startTime).String()
		return nil, output, fmt.Errorf("file processing failed: %w", err)
	}

	// Populate output with extracted metadata
	output.FileName = fileData.FileName
	output.Hash = fileData.Hash
	output.Duration = fileData.Duration
	output.SampleRate = fileData.SampleRate
	output.TimestampLocal = fileData.TimestampLocal
	output.IsAudioMoth = fileData.IsAudioMoth

	// Phase 5: Ensure cluster path is set
	if err := ensureClusterPath(dbPath, input.ClusterID, filepath.Dir(input.FilePath)); err != nil {
		return nil, output, fmt.Errorf("failed to set cluster path: %w", err)
	}

	// Phase 6: Insert into database
	fileID, isDuplicate, err := insertFileIntoDB(
		dbPath,
		fileData,
		input.DatasetID,
		input.ClusterID,
		input.LocationID,
	)
	if err != nil {
		errMsg := err.Error()
		output.Error = &errMsg
		output.ProcessingTime = time.Since(startTime).String()
		return nil, output, fmt.Errorf("database insertion failed: %w", err)
	}

	output.FileID = fileID
	output.IsDuplicate = isDuplicate
	output.ProcessingTime = time.Since(startTime).String()

	return &mcp.CallToolResult{}, output, nil
}

// validateFilePath validates the file exists, is a regular file, is a WAV file, and is not empty
func validateFilePath(filePath string) (os.FileInfo, error) {
	// Check file exists
	info, err := os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, fmt.Errorf("file does not exist: %s", filePath)
		}
		return nil, fmt.Errorf("cannot access file: %w", err)
	}

	// Check it's a regular file
	if !info.Mode().IsRegular() {
		return nil, fmt.Errorf("path is not a regular file: %s", filePath)
	}

	// Check extension is .wav (case-insensitive)
	ext := strings.ToLower(filepath.Ext(filePath))
	if ext != ".wav" {
		return nil, fmt.Errorf("file must be a WAV file (got extension: %s)", ext)
	}

	// Check file is not empty
	if info.Size() == 0 {
		return nil, fmt.Errorf("file is empty: %s", filePath)
	}

	return info, nil
}

// processFile extracts all metadata from a single file
func processFile(filePath string, location *locationData) (*fileData, error) {
	result, err := utils.ProcessSingleFile(filePath, location.Latitude, location.Longitude, location.TimezoneID, true)
	if err != nil {
		return nil, err
	}

	return &fileData{
		FileName:       result.FileName,
		Hash:           result.Hash,
		Duration:       result.Duration,
		SampleRate:     result.SampleRate,
		TimestampLocal: result.TimestampLocal,
		IsAudioMoth:    result.IsAudioMoth,
		MothData:       result.MothData,
		AstroData:      result.AstroData,
	}, nil
}

// insertFileIntoDB inserts a single file into the database
// Returns (fileID, isDuplicate, error)
func insertFileIntoDB(
	dbPath string,
	fileData *fileData,
	datasetID, clusterID, locationID string,
) (string, bool, error) {
	// Open writable database
	database, err := db.OpenWriteableDB(dbPath)
	if err != nil {
		return "", false, fmt.Errorf("failed to open database: %w", err)
	}
	defer database.Close()

	// Begin transaction
	ctx := context.Background()
	tx, err := database.BeginTx(ctx, nil)
	if err != nil {
		return "", false, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback() // Rollback if not committed

	// Check for duplicate hash
	existingID, isDup, err := utils.CheckDuplicateHash(tx, fileData.Hash)
	if err != nil {
		return "", false, err
	}
	if isDup {
		return existingID, true, nil
	}

	// Generate file ID
	fileID, err := utils.GenerateLongID()
	if err != nil {
		return "", false, fmt.Errorf("ID generation failed: %w", err)
	}

	// Insert file record
	_, err = tx.ExecContext(ctx, `
		INSERT INTO file (
			id, file_name, xxh64_hash, location_id, timestamp_local,
			cluster_id, duration, sample_rate, maybe_solar_night, maybe_civil_night,
			moon_phase, created_at, last_modified, active
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, now(), now(), true)
	`,
		fileID, fileData.FileName, fileData.Hash, locationID,
		fileData.TimestampLocal, clusterID, fileData.Duration, fileData.SampleRate,
		fileData.AstroData.SolarNight, fileData.AstroData.CivilNight, fileData.AstroData.MoonPhase,
	)
	if err != nil {
		return "", false, fmt.Errorf("file insert failed: %w", err)
	}

	// Insert file_dataset junction
	_, err = tx.ExecContext(ctx, `
		INSERT INTO file_dataset (file_id, dataset_id, created_at, last_modified)
		VALUES (?, ?, now(), now())
	`, fileID, datasetID)
	if err != nil {
		return "", false, fmt.Errorf("file_dataset insert failed: %w", err)
	}

	// If AudioMoth, insert moth_metadata
	if fileData.IsAudioMoth && fileData.MothData != nil {
		_, err = tx.ExecContext(ctx, `
			INSERT INTO moth_metadata (
				file_id, timestamp, recorder_id, gain, battery_v, temp_c,
				created_at, last_modified, active
			) VALUES (?, ?, ?, ?, ?, ?, now(), now(), true)
		`,
			fileID,
			fileData.MothData.Timestamp,
			&fileData.MothData.RecorderID,
			&fileData.MothData.Gain,
			&fileData.MothData.BatteryV,
			&fileData.MothData.TempC,
		)
		if err != nil {
			return "", false, fmt.Errorf("moth_metadata insert failed: %w", err)
		}
	}

	// Commit transaction
	if err = tx.Commit(); err != nil {
		return "", false, fmt.Errorf("transaction commit failed: %w", err)
	}

	return fileID, false, nil
}

// getLocationData retrieves location coordinates and timezone from database
func getLocationData(dbPath, locationID string) (*locationData, error) {
	database, err := db.OpenReadOnlyDB(dbPath)
	if err != nil {
		return nil, err
	}
	defer database.Close()

	var loc locationData
	err = database.QueryRow(
		"SELECT latitude, longitude, timezone_id FROM location WHERE id = ?",
		locationID,
	).Scan(&loc.Latitude, &loc.Longitude, &loc.TimezoneID)

	if err != nil {
		return nil, fmt.Errorf("failed to query location data: %w", err)
	}

	return &loc, nil
}

// ensureClusterPath sets the cluster's path field if it's currently empty
func ensureClusterPath(dbPath, clusterID, folderPath string) error {
	database, err := db.OpenWriteableDB(dbPath)
	if err != nil {
		return fmt.Errorf("failed to open database: %w", err)
	}
	defer database.Close()

	return utils.EnsureClusterPath(database, clusterID, folderPath)
}